diff --git a/lan/lan_en.json b/lan/lan_en.json index e9cf111d..2e60a8c3 100644 --- a/lan/lan_en.json +++ b/lan/lan_en.json @@ -1159,5 +1159,6 @@ "请确认后再继续": "Please confirm before continuing", "需要相机权限": "Camera permission required", "此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "The activation and deactivation of this feature can only be done through Bluetooth on the phone near the lock", - "网关添加成功": "Gateway added successfully" + "网关添加成功": "Gateway added successfully", + "语音包设置": "Voice packet settings" } diff --git a/lan/lan_keys.json b/lan/lan_keys.json index c289bd0e..9f4edfab 100755 --- a/lan/lan_keys.json +++ b/lan/lan_keys.json @@ -1101,7 +1101,7 @@ "支持的国家": "支持的国家", "支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本", "操作流程": "操作流程", - "操作流程值":"1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了", + "操作流程值": "1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了", "Google Home": "Google Home", "Action name": "Action name", "ScienerSmart": "ScienerSmart", @@ -1164,5 +1164,6 @@ "请确认后再继续": "请确认后再继续", "需要相机权限": "需要相机权限", "此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行", - "网关添加成功": "网关添加成功" + "网关添加成功": "网关添加成功", + "语音包设置": "语音包设置", } diff --git a/lan/lan_zh.json b/lan/lan_zh.json index c856e8bb..633e2931 100755 --- a/lan/lan_zh.json +++ b/lan/lan_zh.json @@ -1166,5 +1166,6 @@ "请确认后再继续": "请确认后再继续", "需要相机权限": "需要相机权限", "一键登录": "一键登录", - "网关添加成功": "网关添加成功" + "网关添加成功": "网关添加成功", + "语音包设置": "语音包设置" } diff --git a/lib/appRouters.dart b/lib/appRouters.dart index 6ff7ac91..1bba3087 100755 --- a/lib/appRouters.dart +++ b/lib/appRouters.dart @@ -26,6 +26,7 @@ import 'package:star_lock/main/lockDetail/lockSet/faceUnlock/faceUnlock_page.dar import 'package:star_lock/main/lockDetail/lockSet/liveVideo/liveVideo_page.dart'; import 'package:star_lock/main/lockDetail/lockSet/motorPower/motorPower_page.dart'; import 'package:star_lock/main/lockDetail/lockSet/openDoorDirection/openDoorDirection_page.dart'; +import 'package:star_lock/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_page.dart'; import 'package:star_lock/main/lockDetail/messageWarn/addFamily/addFamily_page.dart'; import 'package:star_lock/main/lockDetail/messageWarn/lockUser/lockUser_page.dart'; import 'package:star_lock/main/lockDetail/messageWarn/msgNotification/coerceOpenDoor/coerceFingerprint/coerceFingerprint_page.dart'; @@ -41,6 +42,7 @@ import 'package:star_lock/main/lockDetail/palm/palmList/palmList_page.dart'; import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyDetailChangeDate/passwordKeyDetailChangeDate_page.dart'; import 'package:star_lock/main/lockMian/lockMain/xhj/lockMain_xhj_page.dart'; import 'package:star_lock/mine/about/webviewShow_page.dart'; +import 'package:star_lock/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart'; import 'package:star_lock/mine/mine/safeVerify/safeVerify_page.dart'; import 'package:star_lock/mine/minePersonInfo/minePersonInfoEmail/mineBindPhoneOrEmail_page.dart'; import 'package:star_lock/mine/minePersonInfo/minePersonInfoPage/minePersonInfo_entity.dart'; @@ -59,6 +61,7 @@ import 'package:star_lock/mine/mineSet/transferSmartLock/selectBranch/selectBran import 'package:star_lock/mine/mineSet/transferSmartLock/transferSmartLockList/transferSmartLock_page.dart'; import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_page.dart'; import 'package:star_lock/mine/valueAddedServices/advancedFunctionRecord/advancedFunctionRecord_page.dart'; +import 'package:star_lock/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart'; import 'package:star_lock/mine/valueAddedServices/valueAddedServicesRecord/value_added_services_record_page.dart'; import 'package:star_lock/talk/starChart/views/guide/permission_guidance_page.dart'; import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_page.dart'; @@ -368,6 +371,8 @@ abstract class Routers { '/valueAddedServicesRecordPage'; // 增值服务-记录 static const String valueAddedServicesHighFunctionPage = '/ValueAddedServicesHighFunctionPage'; // 增值服务-高级功能 + static const String valueAddedCloudStoragePage = + '/valueAddedCloudStoragePage'; // 增值服务-云存 static const String valueAddedServicesBuyPage = '/ValueAddedServicesBuyPage'; // 增值服务-购买服务 static const String customSMSTemplateListPage = @@ -436,6 +441,8 @@ abstract class Routers { '/AddLockSelectCountryPage'; // 演示模式锁设置页 static const String faceUnlockPage = '/faceUnlockPage'; //面容开锁设置 static const String motorPowerPage = '/motorPowerPage'; //电机功率设置 + static const String speechLanguageSettingsPage = + '/speechLanguageSettingsPage'; //锁语音包设置 static const String openDoorDirectionPage = '/openDoorDirectionPage'; //开门方向设置 static const String catEyeWorkModePage = '/catEyeWorkModePage'; //猫眼工作模式 static const String msgNotificationPage = '/msgNotificationPage'; //消息提醒 @@ -521,6 +528,8 @@ abstract class Routers { '/imageTransmissionView'; //星图对讲页面(图传) static const String permissionGuidancePage = '/permissionGuidancePage'; // 锁屏权限通知引导页面 + static const String lockVoiceSettingPage = + '/lockVoiceSetting'; // 锁屏权限通知引导页面 } abstract class AppRouters { @@ -904,6 +913,10 @@ abstract class AppRouters { name: Routers.valueAddedServicesHighFunctionPage, page: () => const ValueAddedServicesHighFunctionPage(), ), + GetPage( + name: Routers.valueAddedCloudStoragePage, + page: () => const CloudStoragePage(), + ), GetPage( name: Routers.customSMSTemplateListPage, page: () => const CustomSMSTemplateListPage(), @@ -1024,6 +1037,9 @@ abstract class AppRouters { name: Routers.faceUnlockPage, page: () => const FaceUnlockPage()), GetPage( name: Routers.motorPowerPage, page: () => const MotorPowerPage()), + GetPage( + name: Routers.speechLanguageSettingsPage, + page: () => const SpeechLanguageSettingsPage()), GetPage( name: Routers.openDoorDirectionPage, page: () => const OpenDoorDirectionPage()), @@ -1201,6 +1217,9 @@ abstract class AppRouters { GetPage( name: Routers.permissionGuidancePage, page: () => PermissionGuidancePage()), + GetPage( + name: Routers.lockVoiceSettingPage, + page: () => LockVoiceSetting()), // 插件播放页面 // GetPage(name: Routers.h264View, page: () => H264WebView()), // webview播放页面 ]; diff --git a/lib/blue/io_protocol/io_getDeviceModel.dart b/lib/blue/io_protocol/io_getDeviceModel.dart new file mode 100644 index 00000000..0a086e05 --- /dev/null +++ b/lib/blue/io_protocol/io_getDeviceModel.dart @@ -0,0 +1,48 @@ + +import 'dart:convert'; + +import '../io_reply.dart'; +import '../io_sender.dart'; +import '../io_tool/io_tool.dart'; +import '../io_type.dart'; + +class GetDeviceModelCommand extends SenderProtocol { + GetDeviceModelCommand({ + this.lockID, + }) : super(CommandType.getDeviceModel); + String? lockID; + + + @override + String toString() { + return 'GetDeviceModelCommand{lockID: $lockID}'; + } + + @override + List messageDetail() { + List data = []; + + // 指令类型 + final int type = commandType!.typeValue; + final double typeDouble = type / 256; + final int type1 = typeDouble.toInt(); + final int type2 = type % 256; + data.add(type1); + data.add(type2); + + final int length = utf8.encode(lockID!).length; + data.addAll(utf8.encode(lockID!)); + data = getFixedLengthList(data, 40 - length); + + printLog(data); + return data; + } +} + +class GetDeviceModelReply extends Reply { + GetDeviceModelReply.parseData(CommandType commandType, List dataDetail) + : super.parseData(commandType, dataDetail) { + status = dataDetail[2]; + data = dataDetail; + } +} diff --git a/lib/blue/io_protocol/io_voicePackageConfigure.dart b/lib/blue/io_protocol/io_voicePackageConfigure.dart new file mode 100644 index 00000000..c43d56ab --- /dev/null +++ b/lib/blue/io_protocol/io_voicePackageConfigure.dart @@ -0,0 +1,163 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart' as crypto; + +import '../io_reply.dart'; +import '../io_sender.dart'; +import '../io_tool/io_tool.dart'; +import '../io_type.dart'; +import '../sm4Encipher/sm4.dart'; + +//oat升级 +class VoicePackageConfigure extends SenderProtocol { + VoicePackageConfigure( + {this.lockID, + this.userID, + this.keyID, + this.platform, + this.product, + this.fwSize, + this.fwMD5, + this.needAuthor, + this.signKey, + this.privateKey, + this.token, + this.encrypt = true}) + : super(CommandType.startVoicePackageConfigure); + String? lockID; + String? userID; + String? keyID; + int? platform; + int? product; + int? fwSize; + String? fwMD5; + int? needAuthor; + List? signKey; + List? privateKey; + List? token; + bool encrypt; + + @override + String toString() { + return 'VoicePackageConfigure{lockID: $lockID, userID: $userID, ' + 'keyID: $keyID, platform: $platform, product: $product, ' + ' fwSize: $fwSize, ' + 'fwMD5: $fwMD5, needAuthor: $needAuthor, signKey: $signKey, ' + 'privateKey: $privateKey, token: $token}'; + } + + @override + int identifierValue() { + if (encrypt) { + return super.identifierValue(); + } else { + return 0x20; + } + } + + @override + List messageDetail() { + List data = []; + List ebcData = []; + + // 指令类型 + final int type = commandType!.typeValue; + final double typeDouble = type / 256; + final int type1 = typeDouble.toInt(); + final int type2 = type % 256; + data.add(type1); + data.add(type2); + + // 锁id 40 + final int lockIDLength = utf8.encode(lockID!).length; + data.addAll(utf8.encode(lockID!)); + data = getFixedLengthList(data, 40 - lockIDLength); + + //userID 20 + final int userIDLength = utf8.encode(userID!).length; + data.addAll(utf8.encode(userID!)); + data = getFixedLengthList(data, 20 - userIDLength); + + //platform 2 + final int platform0 = (platform! & 0xFF00) >> 8; + final int platform1 = platform! & 0xFF; + data.add(platform0); + data.add(platform1); + + //product 2 + data.addAll([0, 1]); //先默认是 01 + + + //fwSize 4 + final ByteData bytes = ByteData(4); // 创建一个长度为4的字节数据 + bytes.setInt32(0, fwSize!); + final List byteList = bytes.buffer.asUint8List(); + data.addAll(byteList); + + // 创建一个16字节的字节数组 + final Uint8List result = Uint8List(16); + // 将每个十六进制字符转换为4位二进制数据,并将其存储到结果字节数组中 + for (int i = 0; i < fwMD5!.length; i += 2) { + final String hex = fwMD5!.substring(i, i + 2); + final int byteValue = int.parse(hex, radix: 16); + result[i ~/ 2] = byteValue; + } + data.addAll(result); + // token 长度4 首次请求 Token 填 0,如果锁需要鉴权 操作者身份,则会分配动态口令并在应答消息中返回,二次请求时带上。 当token失效或者第一次发送的时候token为0 + data.addAll(token!); + if (needAuthor == 0) { + //AuthCodeLen 1 + data.add(0); + } else { + final List authCodeData = []; + + //KeyID + authCodeData.addAll(utf8.encode(keyID!)); + + //UserID + authCodeData.addAll(utf8.encode(userID!)); + + //token 4 首次请求 Token 填 0,如果锁需要鉴权操作者身份,则会分配动态口令并在应答消息中返回,二次请求时带上。 + authCodeData.addAll(token ?? []); + + authCodeData.addAll(signKey ?? []); + + // 把KeyID、authUserID、时间戳、公钥通过md5加密之后就是authCode + final crypto.Digest authCode = crypto.md5.convert(authCodeData); + + data.add(authCode.bytes.length); + data.addAll(authCode.bytes); + } + + if ((data.length % 16) != 0) { + final int add = 16 - data.length % 16; + for (int i = 0; i < add; i++) { + data.add(0); + } + } + printLog(data); + + if (encrypt) { + // 拿到数据之后通过LockId进行SM4 ECB加密 key:544d485f633335373034383064613864 + ebcData = SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB); + return ebcData; + } else { + data.add(0); + return data; + } + } +} + +class VoicePackageConfigureReply extends Reply { + VoicePackageConfigureReply.parseData( + CommandType commandType, List dataDetail) + : super.parseData(commandType, dataDetail) { + data = dataDetail; + token = data.sublist(2, 6); + status = data[6]; + errorWithStstus(status); + } + + List token = []; +} diff --git a/lib/blue/io_protocol/io_voicePackageConfigureProcess.dart b/lib/blue/io_protocol/io_voicePackageConfigureProcess.dart new file mode 100644 index 00000000..44d1ca18 --- /dev/null +++ b/lib/blue/io_protocol/io_voicePackageConfigureProcess.dart @@ -0,0 +1,73 @@ +import 'dart:typed_data'; + +import '../io_reply.dart'; +import '../io_sender.dart'; +import '../io_type.dart'; + +//oat升级 +class VoicePackageConfigureProcess extends SenderProtocol { + VoicePackageConfigureProcess({ + this.index, + this.size, + this.data, + }) : super(CommandType.voicePackageConfigureProcess); + int? index; + int? size; + List? data; + + @override + String toString() { + return 'VoicePackageConfigureProcess{index: $index, size: $size, data: $data}'; + } + + @override + List messageDetail() { + final List data = []; + + // 指令类型 + final int type = commandType!.typeValue; + final double typeDouble = type / 256; + final int type1 = typeDouble.toInt(); + final int type2 = type % 256; + data.add(type1); + data.add(type2); + + //index 2 + final ByteData indexBytes = ByteData(2); // 创建一个长度为4的字节数据 + indexBytes.setInt16(0, index!); + final List indexList = indexBytes.buffer.asUint8List(); + data.addAll(indexList); + + //size 2 + final ByteData bytes = ByteData(2); // 创建一个长度为4的字节数据 + bytes.setInt16(0, size!); + final List byteList = bytes.buffer.asUint8List(); + data.addAll(byteList); + + data.addAll(this.data!); + + printLog(data); + //不加密 + return data; + } +} + +class VoicePackageConfigureProcessReply extends Reply { + VoicePackageConfigureProcessReply.parseData( + CommandType commandType, List dataDetail) + : super.parseData(commandType, dataDetail) { + data = dataDetail; + status = data[2]; + errorWithStstus(status); + } +} + +class VoicePackageConfigureConfirmationReply extends Reply { + VoicePackageConfigureConfirmationReply.parseData( + CommandType commandType, List dataDetail) + : super.parseData(commandType, dataDetail) { + data = dataDetail; + status = data[2]; + errorWithStstus(status); + } +} diff --git a/lib/blue/io_tool/io_tool.dart b/lib/blue/io_tool/io_tool.dart index 7720ad67..981b1e8c 100755 --- a/lib/blue/io_tool/io_tool.dart +++ b/lib/blue/io_tool/io_tool.dart @@ -288,16 +288,27 @@ String asciiString(List codeUnits) { } String utf8String(List codeUnits) { - codeUnits.reversed; - final List uniqueList = []; - for (int i = 0; i < codeUnits.length; i++) { - if (codeUnits[i] != 0) { - uniqueList.add(codeUnits[i]); - } + try { + // 只去除结尾的0(常见填充方式) + int realLen = codeUnits.indexWhere((b) => b == 0); + List validBytes = (realLen == -1) ? codeUnits : codeUnits.sublist(0, realLen); + return utf8.decode(validBytes); + } catch (e) { + // 容错处理 + return ''; } - uniqueList.reversed; - return utf8.decode(uniqueList).toString(); } +// String utf8String(List codeUnits) { +// codeUnits.reversed; +// final List uniqueList = []; +// for (int i = 0; i < codeUnits.length; i++) { +// if (codeUnits[i] != 0) { +// uniqueList.add(codeUnits[i]); +// } +// } +// uniqueList.reversed; +// return utf8.decode(uniqueList).toString(); +// } bool compareTwoList({List? list1, List? list2}) { if (list1!.length != list2!.length) { diff --git a/lib/blue/io_type.dart b/lib/blue/io_type.dart index a5f523b4..6a4ceffa 100755 --- a/lib/blue/io_type.dart +++ b/lib/blue/io_type.dart @@ -41,6 +41,10 @@ enum CommandType { startOATUpgrade, //OTA升级开始 0x30E0 processOTAUpgrade, //OTA升级过程 0x30E1 confirmationOTAUpgrade, //OTA升级确认 0x30E2 + startVoicePackageConfigure, //语音包配置开始 0x30A1 + voicePackageConfigureProcess, //语音包配置过程 0x30A2 + voicePackageConfigureConfirmation, //语音包配置确认 0x30A3 + getDeviceModel, //获取设备型号 0x30A4 gatewayConfiguringWifi, //网关配网 0x30F4 gatewayConfiguringWifiResult, //网关配网结果 0x30F5 @@ -189,6 +193,26 @@ extension ExtensionCommandType on CommandType { type = CommandType.confirmationOTAUpgrade; } break; + case 0x30A1: + { + type = CommandType.startVoicePackageConfigure; + } + break; + case 0x30A2: + { + type = CommandType.voicePackageConfigureProcess; + } + break; + case 0x30A3: + { + type = CommandType.voicePackageConfigureConfirmation; + } + break; + case 0x30A4: + { + type = CommandType.getDeviceModel; + } + break; case 0x30F4: { type = CommandType.gatewayConfiguringWifi; @@ -307,6 +331,18 @@ extension ExtensionCommandType on CommandType { case CommandType.gatewayGetStatus: type = 0x30F8; break; + case CommandType.startVoicePackageConfigure: + type = 0x30A1; + break; + case CommandType.voicePackageConfigureProcess: + type = 0x30A2; + break; + case CommandType.voicePackageConfigureConfirmation: + type = 0x30A3; + break; + case CommandType.getDeviceModel: + type = 0x30A4; + break; default: type = 0x300A; break; @@ -322,9 +358,11 @@ extension ExtensionCommandType on CommandType { switch (this) { case CommandType.getLockPublicKey: case CommandType.processOTAUpgrade: + case CommandType.voicePackageConfigureProcess: case CommandType.gatewayGetWifiList: case CommandType.gatewayConfiguringWifi: case CommandType.gatewayGetStatus: + case CommandType.getDeviceModel: //不加密 type = 0x20; break; @@ -428,6 +466,18 @@ extension ExtensionCommandType on CommandType { case 0x30F8: t = '获取网关状态'; break; + case 0x30A1: + t = '语音包配置开始'; + break; + case 0x30A2: + t = '语音包配置过程'; + break; + case 0x30A3: + t = '语音包配置确认'; + break; + case 0x30A4: + t = '获取设备型号'; + break; default: t = '读星锁状态信息'; break; diff --git a/lib/blue/reciver_data.dart b/lib/blue/reciver_data.dart index 0b0f4c61..1a2006e0 100755 --- a/lib/blue/reciver_data.dart +++ b/lib/blue/reciver_data.dart @@ -12,6 +12,7 @@ import 'package:star_lock/blue/io_protocol/io_cleanUpUsers.dart'; import 'package:star_lock/blue/io_protocol/io_deletUser.dart'; import 'package:star_lock/blue/io_protocol/io_editUser.dart'; import 'package:star_lock/blue/io_protocol/io_factoryDataReset.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_readAdminPassword.dart'; @@ -21,6 +22,8 @@ import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart'; import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsNoParameters.dart'; import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart'; import 'package:star_lock/blue/io_protocol/io_timing.dart'; +import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart'; +import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart'; import '../tools/storage.dart'; import 'io_gateway/io_gateway_getStatus.dart'; @@ -292,6 +295,28 @@ class CommandReciverManager { reply = GatewayGetStatusReply.parseData(commandType, data); } break; + case CommandType.getDeviceModel: + { + reply = GetDeviceModelReply.parseData(commandType, data); + } + break; + case CommandType.startVoicePackageConfigure: + { + reply = VoicePackageConfigureReply.parseData(commandType, data); + } + break; + case CommandType.voicePackageConfigureProcess: + { + reply = + VoicePackageConfigureProcessReply.parseData(commandType, data); + } + break; + case CommandType.voicePackageConfigureConfirmation: + { + reply = VoicePackageConfigureConfirmationReply.parseData( + commandType, data); + } + break; case CommandType.generalExtendedCommond: { // 子命令类型 diff --git a/lib/common/XSConstantMacro/XSConstantMacro.dart b/lib/common/XSConstantMacro/XSConstantMacro.dart index e10177c2..5ee8df23 100755 --- a/lib/common/XSConstantMacro/XSConstantMacro.dart +++ b/lib/common/XSConstantMacro/XSConstantMacro.dart @@ -90,6 +90,7 @@ class XSConstantMacro { static int webBuyTypeVip = 3; //VIP购买 static int webBuyTypeAuth = 4; //实名购买 static int webBuyTypeShop = 5; //商城购买 + static int webBuyTypeCloudStorage = 6; //云存购买 //设备类型信息 Future> getDeviceInfoData() async { diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 1155117f..da62eff6 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -783,7 +783,7 @@ class LockDetailLogic extends BaseGetXController { // } /// 发送监控消息 - void sendMonitorMessage() { + void sendMonitorMessage() async { final catEyeConfig = state.keyInfos.value.lockSetting?.catEyeConfig ?? []; final network = state.keyInfos.value.network; if (catEyeConfig.isNotEmpty && @@ -840,24 +840,48 @@ class LockDetailLogic extends BaseGetXController { state.DetailLockInfo = eventBus .on() .listen((PassCurrentLockInformationEvent event) { - if (event.lockSetInfoData.lockSettingInfo != null && - event.lockSetInfoData.lockSettingInfo!.catEyeConfig != null && - event.lockSetInfoData.lockSettingInfo!.catEyeConfig!.length > 0) { - if (state.keyInfos.value.lockSetting!.catEyeConfig == null || - state.keyInfos.value.lockSetting!.catEyeConfig!.length == 0) { - state.keyInfos.value.lockSetting!.catEyeConfig!.add(CatEyeConfig( + // 提取重复表达式为局部变量,避免多次解包 + final lockSettingInfo = event.lockSetInfoData.lockSettingInfo; + final lockBasicInfo = event.lockSetInfoData.lockBasicInfo; + final networkInfo = lockBasicInfo?.networkInfo; + final targetCatEyeConfig = lockSettingInfo?.catEyeConfig; + + if (lockBasicInfo != null && networkInfo != null) { + state.keyInfos.value.network?.peerId = networkInfo.peerId; + state.keyInfos.value.network?.wifiName = networkInfo.wifiName; + } + + // 检查前置条件:事件数据有效且 catEyeConfig 非空且非空列表 + if (lockSettingInfo != null && + targetCatEyeConfig != null && + targetCatEyeConfig.isNotEmpty) { + // 当前状态的 catEyeConfig + final stateCatEyeConfig = + state.keyInfos.value.lockSetting?.catEyeConfig; + + // 如果状态中的 catEyeConfig 为空或空列表,则初始化默认配置 + if (stateCatEyeConfig == null || stateCatEyeConfig.isEmpty) { + state.keyInfos.value.lockSetting?.catEyeConfig ??= []; + state.keyInfos.value.lockSetting!.catEyeConfig!.add( + CatEyeConfig( catEyeMode: 0, catEyeModeConfig: CatEyeModeConfig( - realTimeMode: 0, - recordEndTime: 0, - recordMode: 0, - recordStartTime: 0, - recordTime: '0', - detectionDistance: '0'))); + realTimeMode: 0, + recordEndTime: 0, + recordMode: 0, + recordStartTime: 0, + recordTime: '0', + detectionDistance: '0', + ), + ), + ); } - state.keyInfos.value.lockSetting!.catEyeConfig![0].catEyeMode = event - .lockSetInfoData.lockSettingInfo!.catEyeConfig![0].catEyeMode ?? - 1; + + // 更新 catEyeMode,使用空值合并提供默认值 1 + state.keyInfos.value.lockSetting!.catEyeConfig![0].catEyeMode = + targetCatEyeConfig[0].catEyeMode ?? 1; + + // 刷新状态 state.keyInfos.refresh(); } }); diff --git a/lib/main/lockDetail/lockDetail/passthrough_item.dart b/lib/main/lockDetail/lockDetail/passthrough_item.dart new file mode 100644 index 00000000..bb9a910c --- /dev/null +++ b/lib/main/lockDetail/lockDetail/passthrough_item.dart @@ -0,0 +1,281 @@ +// 音色项类 +import 'package:get/get.dart'; + +class TimbreItem { + final String timbre; + final String name; + final String timbrePackUrl; + + TimbreItem({ + required this.timbre, + required this.name, + required this.timbrePackUrl, + }); + + factory TimbreItem.fromJson(Map json) { + return TimbreItem( + timbre: json['timbre'] ?? '', + name: json['name'] ?? '', + timbrePackUrl: json['timbrePackUrl'] ?? '', + ); + } + + Map toJson() { + return { + 'timbre': timbre, + 'name': name, + 'timbrePackUrl': timbrePackUrl, + }; + } + + @override + String toString() { + return 'TimbreItem{timbre: $timbre, name: $name, timbrePackUrl: $timbrePackUrl}'; + } +} + +// 透传项主类 +class PassthroughItem { + final String lang; + final List timbres; + + PassthroughItem({ + required this.lang, + required this.timbres, + }); + + factory PassthroughItem.fromJson(Map json) { + var timbresJson = json['timbres'] as List?; + List timbresList = timbresJson != null + ? timbresJson + .map((e) => TimbreItem.fromJson(e as Map)) + .toList() + : []; + return PassthroughItem( + lang: json['lang'] ?? '', + timbres: timbresList, + ); + } + + Map toJson() { + return { + 'lang': lang, + 'timbres': timbres.map((e) => e.toJson()).toList(), + }; + } + + @override + String toString() { + return 'PassthroughItem{lang: $lang, timbres: $timbres}'; + } +} + +class PassthroughListResponse { + PassthroughListResponse({ + this.description, + this.errorCode, + this.data, + this.errorMsg, + }); + + String? description; + int? errorCode; + List? data; + String? errorMsg; + + PassthroughListResponse.fromJson(dynamic json) { + description = json['description']; + errorCode = json['errorCode']; + if (json['data'] != null && json['data'] is List) { + data = (json['data'] as List) + .map((e) => PassthroughItem.fromJson(e as Map)) + .toList(); + } else { + data = null; + } + errorMsg = json['errorMsg']; + } + + Map toJson() { + final map = {}; + map['description'] = description; + map['errorCode'] = errorCode; + if (data != null) { + map['data'] = data!.map((e) => e.toJson()).toList(); + } + map['errorMsg'] = errorMsg; + return map; + } +} + +/// 工具方法:通过语言编码获取本地化文本 +/// 例如:'zh_CN' -> '简体中文'.tr,'en_US' -> '英文'.tr +class PassthroughLangHelper { + static String getLangText(String langCode) { + switch (langCode.toLowerCase()) { + case 'zh_cn': + return '简体中文'.tr; + case 'zh_tw': + return '繁体中文(中国台湾)'.tr; + case 'zh_hk': + return '繁体中文(中国香港)'.tr; + case 'en_us': + return '英文'.tr; + case 'fr_fr': + return '法语'.tr; + case 'ru_ru': + return '俄语'.tr; + case 'de_de': + return '德语'.tr; + case 'ja_jp': + return '日语'.tr; + case 'ko_kr': + return '韩语'.tr; + case 'it_it': + return '意大利语'.tr; + case 'pt_pt': + return '葡萄牙语'.tr; + case 'es_es': + return '西班牙语'.tr; + case 'ar_sa': + return '阿拉伯语'.tr; + case 'vi_vn': + return '越南语'.tr; + case 'ms_my': + return '马来语'.tr; + case 'nl_nl': + return '荷兰语'.tr; + case 'ro_ro': + return '罗马尼亚语'.tr; + case 'lt_lt': + return '立陶宛语'.tr; + case 'sv_se': + return '瑞典语'.tr; + case 'et_ee': + return '爱沙尼亚语'.tr; + case 'pl_pl': + return '波兰语'.tr; + case 'sk_sk': + return '斯洛伐克语'.tr; + case 'cs_cz': + return '捷克语'.tr; + case 'el_gr': + return '希腊语'.tr; + case 'he_il': + return '希伯来语'.tr; + case 'sr_rs': + return '塞尔维亚语'.tr; + case 'tr_tr': + return '土耳其语'.tr; + case 'hu_hu': + return '匈牙利语'.tr; + case 'bg_bg': + return '保加利亚语'.tr; + case 'kk_kz': + return '哈萨克斯坦语'.tr; + case 'bn_bd': + return '孟加拉语'.tr; + case 'hr_hr': + return '克罗地亚语'.tr; + case 'th_th': + return '泰语'.tr; + case 'id_id': + return '印度尼西亚语'.tr; + case 'fi_fi': + return '芬兰语'.tr; + case 'da_dk': + return '丹麦语'.tr; + case 'uk_ua': + return '乌克兰语'.tr; + case 'hi_in': + return '印地语'.tr; + case 'ur_pk': + return '乌尔都语'.tr; + case 'hy_am': + return '亚美尼亚语'.tr; + case 'ka_ge': + return '格鲁吉亚语'.tr; + default: + return '未知语言'.tr; + } + } +} + +class ValidityPeriodResponse { + ValidityPeriodResponse({ + this.description, + this.errorCode, + this.data, + this.errorMsg, + }); + + String? description; + int? errorCode; + ValidityPeriod? data; + String? errorMsg; + + ValidityPeriodResponse.fromJson(dynamic json) { + description = json['description']; + errorCode = json['errorCode']; + if (json['data'] != null) { + data = ValidityPeriod.fromJson(json['data']); + } else { + data = null; + } + errorMsg = json['errorMsg']; + } + + Map toJson() { + final map = {}; + map['description'] = description; + map['errorCode'] = errorCode; + if (data != null) { + map['data'] = data!.toJson(); + } + map['errorMsg'] = errorMsg; + return map; + } +} + +class ValidityPeriod { + final int lockId; + final int status; + final String validityPeriodStart; + final String validityPeriodEnd; + final int rollingStorageDays; + final int remainingDays; + + ValidityPeriod({ + required this.lockId, + required this.status, + required this.validityPeriodStart, + required this.validityPeriodEnd, + required this.rollingStorageDays, + required this.remainingDays, + }); + + // 反序列化方法 + factory ValidityPeriod.fromJson(Map json) { + return ValidityPeriod( + lockId: json['lockId'] as int? ?? 0, + // 默认值为 0 + status: json['status'] as int? ?? 0, + validityPeriodStart: json['validityPeriodStart'] as String? ?? '', + validityPeriodEnd: json['validityPeriodEnd'] as String? ?? '', + rollingStorageDays: json['rollingStorageDays'] as int? ?? 0, + remainingDays: json['remainingDays'] as int? ?? 0, + ); + } + + // 序列化方法 + Map toJson() { + return { + 'lockId': lockId, + 'status': status, + 'validityPeriodStart': validityPeriodStart, + 'validityPeriodEnd': validityPeriodEnd, + 'rollingStorageDays': rollingStorageDays, + 'remainingDays': remainingDays, + }; + } +} diff --git a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart index 6f0a4570..6b3451fe 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart @@ -161,11 +161,12 @@ class ConfiguringWifiLogic extends BaseGetXController { // 上报服务器 - 注意: sureBtnState 状态将在 updateNetworkInfo 方法中或其回调中完成重置 final info = await updateNetworkInfo( - peerId: peerId, - wifiName: wifiName ?? '', - secretKey: secretKey, - deviceMac: deviceMac ?? '', - networkMac: networkMac ?? ''); + peerId: peerId, + wifiName: wifiName ?? '', + secretKey: secretKey, + deviceMac: deviceMac ?? '', + networkMac: networkMac ?? '', + ); if (info.errorCode!.codeIsSuccessful) { // 设置锁的peerID StartChartManage().lockNetworkInfo = DeviceNetworkInfo( @@ -175,6 +176,11 @@ class ConfiguringWifiLogic extends BaseGetXController { peerId: peerId, ); + state.lockSetInfoData.value?.lockBasicInfo?.networkInfo?.peerId = + peerId; + state.lockSetInfoData.value?.lockBasicInfo?.networkInfo?.wifiName = + wifiName; + /// 配网成功后,赋值锁的peerId StartChartManage().lockPeerId = peerId; @@ -189,7 +195,9 @@ class ConfiguringWifiLogic extends BaseGetXController { Get.offAllNamed(Routers.starLockMain); } eventBus.fire(SuccessfulDistributionNetwork()); - eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true)); + eventBus.fire(RefreshLockListInfoDataEvent( + clearScanDevices: true, isUnShowLoading: true)); + eventBus.fire(RefreshLockDetailInfoDataEvent()); }); // 获取锁设置 @@ -589,7 +597,6 @@ class ConfiguringWifiLogic extends BaseGetXController { recordType: recordType, records: records, isUnShowLoading: true); - if (entity.errorCode!.codeIsSuccessful) { eventBus .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); diff --git a/lib/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart b/lib/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart index 5127c0b5..ccda689f 100755 --- a/lib/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart +++ b/lib/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart @@ -158,6 +158,7 @@ class LockFeature { this.isElectronicAntiLock, this.isVisualDoorBellCode, this.isDoubleLockLinkage, + this.languageSpeech, }); LockFeature.fromJson(Map json) { @@ -218,6 +219,7 @@ class LockFeature { isElectronicAntiLock = json['isElectronicAntiLock']; isVisualDoorBellCode = json['isVisualDoorBellCode']; isDoubleLockLinkage = json['isDoubleLockLinkage']; + languageSpeech = json['languageSpeech']; } int? password; @@ -277,6 +279,7 @@ class LockFeature { int? isElectronicAntiLock; int? isVisualDoorBellCode; int? isDoubleLockLinkage; + int? languageSpeech; Map toJson() { final Map data = {}; @@ -337,6 +340,7 @@ class LockFeature { data['isElectronicAntiLock'] = isElectronicAntiLock; data['isVisualDoorBellCode'] = isVisualDoorBellCode; data['isDoubleLockLinkage'] = isDoubleLockLinkage; + data['languageSpeech'] = languageSpeech; return data; } } @@ -348,6 +352,8 @@ class LockBasicInfo { this.keyId, this.model, this.electricQuantity, + this.hwVersion, + this.fwVersion, this.electricQuantityStandby, this.indate, this.isLockOwner, @@ -376,6 +382,8 @@ class LockBasicInfo { keyId = json['keyId']; model = json['model']; electricQuantity = json['electricQuantity']; + hwVersion = json['hwVersion']; + fwVersion = json['fwVersion']; electricQuantityStandby = json['electricQuantityStandby']; indate = json['indate']; isLockOwner = json['isLockOwner']; @@ -411,6 +419,8 @@ class LockBasicInfo { int? keyId; String? model; int? electricQuantity; + String? fwVersion; + String? hwVersion; int? electricQuantityStandby; int? indate; int? isLockOwner; @@ -440,6 +450,8 @@ class LockBasicInfo { data['keyId'] = keyId; data['model'] = model; data['electricQuantity'] = electricQuantity; + data['hwVersion'] = hwVersion; + data['fwVersion'] = fwVersion; data['electricQuantityStandby'] = electricQuantityStandby; data['indate'] = indate; data['isLockOwner'] = isLockOwner; @@ -523,6 +535,7 @@ class LockSettingInfo { this.autoLightScreen, this.autoLightScreenTime, this.faceEnErrUnlock, + this.currentVoiceTimbre, }); LockSettingInfo.fromJson(Map json) { @@ -570,6 +583,10 @@ class LockSettingInfo { autoLightScreen = json['autoLightScreen']; autoLightScreenTime = json['autoLightScreenTime']; faceEnErrUnlock = json['faceEnErrUnlock']; + if (json['currentVoiceTimbre'] != null) { + currentVoiceTimbre = + CurrentVoiceTimbre.fromJson(json['currentVoiceTimbre']); + } } int? remoteUnlock; @@ -605,6 +622,7 @@ class LockSettingInfo { int? autoLightScreen; //猫眼-自动亮屏开关 0:关闭 1:开启 int? autoLightScreenTime; //猫眼-自动亮屏时间 int? faceEnErrUnlock; + CurrentVoiceTimbre? currentVoiceTimbre; Map toJson() { final Map data = {}; @@ -645,7 +663,9 @@ class LockSettingInfo { data['autoLightScreen'] = autoLightScreen; data['autoLightScreenTime'] = autoLightScreenTime; data['faceEnErrUnlock'] = faceEnErrUnlock; - + if (currentVoiceTimbre != null) { + data['currentVoiceTimbre'] = currentVoiceTimbre!.toJson(); + } return data; } } @@ -757,3 +777,29 @@ class CatEyeModeConfig { return data; } } + +class CurrentVoiceTimbre { + String? lang; + String? timbre; + + CurrentVoiceTimbre({ + this.lang, + this.timbre, + }); + + // 将对象转换为 JSON 数据 + Map toJson() { + final Map data = {}; + data['lang'] = lang; + data['timbre'] = timbre; + return data; + } + + // 从 JSON 数据构造对象 + factory CurrentVoiceTimbre.fromJson(Map json) { + return CurrentVoiceTimbre( + timbre: json['timbre'], + lang: json['lang'], + ); + } +} diff --git a/lib/main/lockDetail/lockSet/lockSet/lockSet_logic.dart b/lib/main/lockDetail/lockSet/lockSet/lockSet_logic.dart index 7d6b4a65..e27e1b4d 100755 --- a/lib/main/lockDetail/lockSet/lockSet/lockSet_logic.dart +++ b/lib/main/lockDetail/lockSet/lockSet/lockSet_logic.dart @@ -4,6 +4,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:get/get.dart'; import 'package:star_lock/apm/apm_helper.dart'; +import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; @@ -66,6 +67,11 @@ class LockSetLogic extends BaseGetXController { (state.ifCurrentScreen.value == true)) { _replyUpdataLockSetReply(reply); } + + if (reply is GetDeviceModelReply) { + // 获取设备型号 + _handlerDeviceModelReply(reply); + } }); } @@ -728,6 +734,7 @@ class LockSetLogic extends BaseGetXController { getUpdataLockSet(); _initReplySubscription(); + sendGetDeviceModelBleMessage(); // _scanListDiscoveredDeviceSubscriptionAction(); } @@ -737,4 +744,52 @@ class LockSetLogic extends BaseGetXController { _passCurrentLockInformationEvent!.cancel(); // _scanListDiscoveredDeviceSubscription.cancel(); } + + // 发送获取型号蓝牙命令 + 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 _handlerDeviceModelReply(GetDeviceModelReply reply) async { + final int status = reply.data[2]; + switch (status) { + case 0x00: + //成功 + cancelBlueConnetctToastTimer(); + // 3. 解析DeviceModel(40字节,索引3~42) + int startIndex = 3; + int length = 40; + List deviceModelBytes = + reply.data.sublist(startIndex, startIndex + length); + + String rawData = String.fromCharCodes(deviceModelBytes); + int firstNullIndex = rawData.indexOf('\u0000'); + String deviceModelValue = rawData.substring(0, firstNullIndex); + print(deviceModelValue); // 输出: 2403 + print('获取到 DeviceModel (原始): $deviceModelValue'); + await Storage.setString(deviceModel, deviceModelValue); + break; + default: + showToast('获取设备型号失败'.tr); + break; + } + } } diff --git a/lib/main/lockDetail/lockSet/lockSet/lockSet_page.dart b/lib/main/lockDetail/lockSet/lockSet/lockSet_page.dart index e4d22536..43cbc989 100755 --- a/lib/main/lockDetail/lockSet/lockSet/lockSet_page.dart +++ b/lib/main/lockDetail/lockSet/lockSet/lockSet_page.dart @@ -526,6 +526,20 @@ class _LockSetPageState extends State 'lockSetInfoData': state.lockSetInfoData.value }); })), + // 锁语音包设置 + Visibility( + visible: state.lockFeature.value.languageSpeech == 1, + child: CommonItem( + leftTitel: '语音包设置'.tr, + rightTitle: '', + isHaveLine: true, + isHaveDirection: true, + action: () { + Get.toNamed(Routers.speechLanguageSettingsPage, + arguments: { + 'lockSetInfoData': state.lockSetInfoData.value + }); + })), // 蓝牙广播(关闭则不能使用蓝牙主动开锁) /* 2024-01-12 会议确定去掉“蓝牙广播” by DaisyWu Obx(() => Visibility( diff --git a/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_logic.dart b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_logic.dart new file mode 100644 index 00000000..921a8ef8 --- /dev/null +++ b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_logic.dart @@ -0,0 +1,366 @@ +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_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'; + +class SpeechLanguageSettingsLogic extends BaseGetXController { + final SpeechLanguageSettingsState state = SpeechLanguageSettingsState(); + StreamSubscription? _replySubscription; + + @override + void onInit() async { + super.onInit(); + _replySubscription = + EventBusManager().eventBus!.on().listen((Reply reply) async { + if (reply is GetDeviceModelReply) { + // 获取设备型号 + _handlerDeviceModelReply(reply); + } + if (reply is VoicePackageConfigureReply) { + // 语言包配置开始 + _handlerStartVoicePackageConfigure(reply); + } else if (reply is VoicePackageConfigureProcessReply) { + _handlerVoicePackageConfigureProcess(reply); + } else if (reply is VoicePackageConfigureConfirmationReply) { + final PassthroughItem item = + state.languages[state.selectPassthroughListIndex.value]; + final timbre = item.timbres[state.selectLanguageIndex.value]; + final LoginEntity entity = + await ApiRepository.to.settingCurrentVoiceTimbre( + data: { + 'lang': item.lang, + 'timbre': timbre.timbre, + }, + lockId: state.lockSetInfoData.value.lockId!, + ); + if (entity.errorCode!.codeIsSuccessful) { + showSuccess('设置成功'.tr, something: () { + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre + ?.lang = item.lang; + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre + ?.timbre = timbre.timbre; + eventBus.fire( + PassCurrentLockInformationEvent(state.lockSetInfoData.value)); + }); + } + dismissEasyLoading(); + } + }); + state.deviceModel.value = await Storage.getString(deviceModel) ?? ''; + debugPrint('设备型号:${state.deviceModel.value}'); + if (state.deviceModel.value != null) { + await initList(); + } + // await sendGetDeviceModelBleMessage(); + } + + + /// 获取列表 + initList() async { + showEasyLoading(); + try { + final PassthroughListResponse entity = await ApiRepository.to + .getPassthroughList(data: {'deviceType': state.deviceModel.value}); + if (entity.errorCode!.codeIsSuccessful) { + state.languages.value = entity.data!!; + final oldTimbre = state + .lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre; + final oldLang = state + .lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang; + + for (int index = 0; index < state.languages.value.length; index++) { + final element = state.languages.value[index]; + final timbres = element.timbres; + for (int i = 0; i < timbres.length; i++) { + final timbre = timbres[i].timbre; + if ((oldLang != null && oldLang == element.lang) && + (oldTimbre != null && oldTimbre == timbre)) { + state.selectPassthroughListIndex.value = index; + state.selectLanguageIndex.value = i; + } + } + } + } + } catch (e) { + debugPrint('获取语音包出现错误:$e'); + } finally { + dismissEasyLoading(); + } + } + + void saveSpeechLanguageSettings() async { + // 如果已经开始发送中则不处理保存点击事件 + if (state.progress.value > 0) { + return; + } + final oldTimbre = + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre; + final oldLang = + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang; + + EasyLoading.showProgress(state.progress.value, status: '正在发送数据'); + final PassthroughItem item = + state.languages[state.selectPassthroughListIndex.value]; + final timbre = item.timbres[state.selectLanguageIndex.value]; + debugPrint('选中的语音是:${timbre}'); + if ((oldLang != null && oldLang == item.lang) && + (oldTimbre != null && oldTimbre == timbre.timbre)) { + showToast('已设置为当前选择的语音包'.tr); + } + await downloadFile(timbre.timbrePackUrl); + } + + 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 _handlerDeviceModelReply(GetDeviceModelReply reply) async { + final int status = reply.data[2]; + switch (status) { + case 0x00: + //成功 + cancelBlueConnetctToastTimer(); + // 3. 解析DeviceModel(40字节,索引3~42) + int startIndex = 3; + int length = 40; + List deviceModelBytes = + reply.data.sublist(startIndex, startIndex + length); + + String rawData = String.fromCharCodes(deviceModelBytes); + int firstNullIndex = rawData.indexOf('\u0000'); + String deviceModel = rawData.substring(0, firstNullIndex); + print(deviceModel); // 输出: 2403 + print('获取到 DeviceModel (原始): $deviceModel'); + state.deviceModel.value = deviceModel; + await initList(); + break; + default: + showToast('获取设备型号失败'.tr); + break; + } + } + + // 开始配置语音包 + 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 _sendNextPackage() { + 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)}%'); + _sendLanguageFileBleMessage( + index: state.voiceSubcontractingIndex, + data: packageData, + ); + } + + _sendLanguageFileBleMessage({required int index, required Uint8List data}) { + BlueManage().blueSendData(BlueManage().connectDeviceName, + (BluetoothConnectionState deviceConnectionState) async { + if (deviceConnectionState == BluetoothConnectionState.connected) { + BlueManage().writeCharacteristicWithResponse( + VoicePackageConfigureProcess( + index: index, + size: data.length, + data: data, + ).packageData(), + ); + } else if (deviceConnectionState == + BluetoothConnectionState.disconnected) { + dismissEasyLoading(); + cancelBlueConnetctToastTimer(); + showBlueConnetctToast(); + } + }); + } + + void _handlerVoicePackageConfigureProcess( + VoicePackageConfigureProcessReply reply) { + 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(); + } +} diff --git a/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_page.dart b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_page.dart new file mode 100644 index 00000000..bb5275a8 --- /dev/null +++ b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_page.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; +import 'package:star_lock/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_logic.dart'; +import 'package:star_lock/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_state.dart'; +import 'package:star_lock/tools/EasyRefreshTool.dart'; +import 'package:star_lock/tools/titleAppBar.dart'; + +class SpeechLanguageSettingsPage extends StatefulWidget { + const SpeechLanguageSettingsPage(); + + @override + State createState() => + _SpeechLanguageSettingsPageState(); +} + +class _SpeechLanguageSettingsPageState + extends State { + final SpeechLanguageSettingsLogic logic = + Get.put(SpeechLanguageSettingsLogic()); + final SpeechLanguageSettingsState state = + Get.find().state; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.mainBackgroundColor, + appBar: TitleAppBar( + barTitle: '锁语音包设置'.tr, + haveBack: true, + backgroundColor: AppColors.mainColor, + actionsList: [ + TextButton( + onPressed: logic.saveSpeechLanguageSettings, + child: Text( + '保存'.tr, + style: TextStyle( + color: Colors.white, + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + body: EasyRefreshTool( + child: _buildBody(), + onRefresh: () { + logic.sendGetDeviceModelBleMessage(); + }, + ), + ); + } + + Widget _buildBody() { + return Obx( + () => SingleChildScrollView( + child: Column( + children: [ + // Container( + // width: 1.sw, + // decoration: BoxDecoration(color: Colors.white), + // child: Column( + // children: [ + // RadioListTile( + // title: Text("男声".tr), + // value: 1, + // groupValue: state.selectedValue.value, + // onChanged: (value) { + // state.selectedValue.value = int.parse(value.toString()); + // }, + // ), + // RadioListTile( + // title: Text("女声".tr), + // value: 2, + // groupValue: state.selectedValue.value, + // onChanged: (value) { + // state.selectedValue.value = int.parse(value.toString()); + // }, + // ) + // ], + // ), + // ), + SizedBox( + height: 8.h, + ), + Column( + children: _buildList(), + ), + ], + ), + ), + ); + } + + List _buildList() { + final languages = state.languages; + return List.generate( + languages.length, + (index) => _buildItem(languages[index], index), + ); + } + + _buildItem(PassthroughItem language, int index) { + final timbres = language.timbres; + final isSelected = state.selectPassthroughListIndex == index; + return ExpansionTile( + title: Text( + PassthroughLangHelper.getLangText(language.lang), + style: TextStyle( + fontSize: 24.sp, + fontWeight: isSelected ? FontWeight.bold : null, + ), + ), + onExpansionChanged: (bool expanded) {}, + initiallyExpanded: false, + backgroundColor: Colors.white, + collapsedBackgroundColor: Colors.white, + expandedCrossAxisAlignment: CrossAxisAlignment.center, + expandedAlignment: Alignment.center, + shape: InputBorder.none, + maintainState: true, + // 去除展开状态下的边框 + collapsedShape: InputBorder.none, + // 去除折叠状态下的边框 + childrenPadding: EdgeInsets.only(left: 12.w), + children: List.generate( + timbres.length, + (int languageIndex) => ListTile( + title: Text( + timbres[languageIndex].name, + style: TextStyle( + fontSize: 22.sp, + fontWeight: + state.selectLanguageIndex == languageIndex && isSelected + ? FontWeight.bold + : null, + ), + ), + trailing: state.selectLanguageIndex == languageIndex && isSelected + ? Icon( + Icons.check_circle, + color: AppColors.mainColor, + ) // 仅当选中时显示图标 + : null, // 默认不显示 + onTap: () { + // 更新选中的语音包 + state.selectLanguageIndex.value = languageIndex; + // 更新选中的语言 + state.selectPassthroughListIndex.value = index; + }, // 默认图标, // 右侧的图标 + ), + ), + ); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + if (EasyLoading.isShow) { + EasyLoading.dismiss(animation: true); + } + } +} diff --git a/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_state.dart b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_state.dart new file mode 100644 index 00000000..02079884 --- /dev/null +++ b/lib/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_state.dart @@ -0,0 +1,46 @@ +import 'dart:typed_data'; + +import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; +import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; + +class SpeechLanguageSettingsState { + SpeechLanguageSettingsState() { + final map = Get.arguments; + lockSetInfoData.value = map['lockSetInfoData']; + } + + Rx lockSetInfoData = LockSetInfoData().obs; + + // 选中的语音包列表下标 + RxInt selectPassthroughListIndex = 0.obs; + + // 选中的语音下标 + RxInt selectLanguageIndex = 0.obs; + + final RxList languages = [].obs; + + Map languageSpeechDeviceTypeMapping = {0: '2403'}; + + RxBool otaUpdateIng = false.obs; + RxDouble otaProgress = 0.00.obs; + RxString deviceModel = '2403'.obs; + Uint8List? data; + + // 语音包最大大小 + int voiceSubcontractingSize = 256; + + // 总数据包数量 + int voiceSubcontractingCount = 0; + + // 数据包序列号 + int voiceSubcontractingIndex = 0; + + // 分包发送进度(0.0~1.0) + RxDouble progress = 0.0.obs; + + RxInt selectedValue = 1.obs; + + +} diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart index e90c9793..35bc490c 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart @@ -2,8 +2,12 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:star_lock/appRouters.dart'; +import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; +import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; @@ -142,10 +146,45 @@ class VideoLogLogic extends BaseGetXController { } } - @override - onReady() { - super.onReady(); + getLockPassthroughInfo() async { + showEasyLoading(); + try { + var entity = await ApiRepository.to.getValidityPeriodInfo( + lockId: state.getLockId.value, + ); + if (entity.errorCode!.codeIsSuccessful) { + state.validityPeriodInfo.value = entity.data!; + } + } catch (e) { + } finally { + dismissEasyLoading(); + } + } + @override + void onInit() async { + await getLockPassthroughInfo(); + super.onInit(); + } + + @override + onReady() async { + super.onReady(); getLockCloudStorageList(); } + + getWebPlayUrl() async { + final AdvancedFeaturesWebEntity entity = + await ApiRepository.to.getServicePackageBuyUrl(); + if (entity.errorCode!.codeIsSuccessful) { + state.cloudStorageWebViewUrl.value = entity.data!.cloudStorage!; + final uploadReportBuyRequest = await ApiRepository.to + .uploadReportBuyRequest(lockId: state.getLockId.value); + if (uploadReportBuyRequest.errorCode!.codeIsSuccessful) { + Get.toNamed(Routers.advancedFeaturesWebPage, arguments: { + 'webBuyType': XSConstantMacro.webBuyTypeCloudStorage, + }); + } + } + } } diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart index 5e83bee2..0167d59b 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart @@ -26,7 +26,9 @@ class VideoLogPage extends StatefulWidget { class _VideoLogPageState extends State { final VideoLogLogic logic = Get.put(VideoLogLogic()); - final VideoLogState state = Get.find().state; + final VideoLogState state = Get + .find() + .state; @override void initState() { @@ -54,64 +56,66 @@ class _VideoLogPageState extends State { // title加编辑按钮 editVideoTip(), Obx( - () => Visibility( - visible: !state.isNavLocal.value, - child: state.videoLogList.length > 0 - ? Expanded( - child: ListView.builder( - itemCount: state.videoLogList.length, - itemBuilder: (BuildContext c, int index) { - final CloudStorageData item = - state.videoLogList[index]; - return Column( - children: [ - Container( - margin: EdgeInsets.only( - left: 20.w, top: 15.w, bottom: 15.w), - child: Row(children: [ - Text(item.date ?? '', - style: TextStyle(fontSize: 20.sp)), - ])), - mainListView(index, item) - ], - ); - }, - ), - ) - : _buildNotData(), - ), + () => + Visibility( + visible: !state.isNavLocal.value, + child: state.videoLogList.length > 0 + ? Expanded( + child: ListView.builder( + itemCount: state.videoLogList.length, + itemBuilder: (BuildContext c, int index) { + final CloudStorageData item = + state.videoLogList[index]; + return Column( + children: [ + Container( + margin: EdgeInsets.only( + left: 20.w, top: 15.w, bottom: 15.w), + child: Row(children: [ + Text(item.date ?? '', + style: TextStyle(fontSize: 20.sp)), + ])), + mainListView(index, item) + ], + ); + }, + ), + ) + : _buildNotData(), + ), ), // 本地顶部 Obx( - () => Visibility( - visible: state.isNavLocal.value, - child: state.lockVideoList.length > 0 - ? Expanded( - child: ListView.builder( - itemCount: state.lockVideoList.length, - itemBuilder: (BuildContext c, int index) { - final CloudStorageData item = - state.lockVideoList[index]; - return Column( - children: [ - Container( - margin: EdgeInsets.only( - left: 20.w, top: 15.w, bottom: 15.w), - child: Row( - children: [ - Text(item.date ?? '', - style: TextStyle(fontSize: 20.sp)), - ], - ), + () => + Visibility( + visible: state.isNavLocal.value, + child: state.lockVideoList.length > 0 + ? Expanded( + child: ListView.builder( + itemCount: state.lockVideoList.length, + itemBuilder: (BuildContext c, int index) { + final CloudStorageData item = + state.lockVideoList[index]; + return Column( + children: [ + Container( + margin: EdgeInsets.only( + left: 20.w, top: 15.w, bottom: 15.w), + child: Row( + children: [ + Text(item.date ?? '', + style: TextStyle(fontSize: 20.sp)), + ], ), - lockMainListView(index, item) - ], - ); - }, - ), - ) - : _buildNotData(), - ), + ), + lockMainListView(index, item) + ], + ); + }, + ), + ) + : _buildNotData(), + ), ), ], ), @@ -157,13 +161,14 @@ class _VideoLogPageState extends State { // logic.clearDownloads(); }); }, - child: Obx(() => Text('云存'.tr, - style: state.isNavLocal.value == true - ? TextStyle( + child: Obx(() => + Text('云存'.tr, + style: state.isNavLocal.value == true + ? TextStyle( color: Colors.grey, fontSize: 26.sp, fontWeight: FontWeight.w600) - : TextStyle( + : TextStyle( color: Colors.white, fontSize: 28.sp, fontWeight: FontWeight.w600)))), @@ -175,18 +180,19 @@ class _VideoLogPageState extends State { }); }, child: Obx( - () => Text( - '已下载'.tr, - style: state.isNavLocal.value == true - ? TextStyle( + () => + Text( + '已下载'.tr, + style: state.isNavLocal.value == true + ? TextStyle( color: Colors.white, fontSize: 28.sp, fontWeight: FontWeight.w600) - : TextStyle( + : TextStyle( color: Colors.grey, fontSize: 26.sp, fontWeight: FontWeight.w600), - ), + ), ), ), ], @@ -197,41 +203,120 @@ class _VideoLogPageState extends State { // 云存顶部视频 Widget vipTip() { return GestureDetector( - onTap: () { - Get.toNamed(Routers.valueAddedServicesHighFunctionPage); + onTap: () async { + await logic.getWebPlayUrl(); + // Get.toNamed(Routers.valueAddedCloudStoragePage); + // Get.toNamed(Routers.valueAddedServicesHighFunctionPage); }, child: Container( // height: 150.h, margin: EdgeInsets.all(15.w), padding: - EdgeInsets.only(left: 20.w, top: 20.w, bottom: 20.w, right: 10.w), + EdgeInsets.only(left: 20.w, top: 20.w, bottom: 20.w, right: 10.w), decoration: BoxDecoration( color: const Color(0xFFF6F7F8), borderRadius: BorderRadius.circular(20.h)), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('3天滚动储存'.tr, style: TextStyle(fontSize: 24.sp)), - SizedBox(height: 10.h), - Text("${F.navTitle}${"已为本设备免费提供3大滚动视频储存服务".tr}", - style: TextStyle(fontSize: 22.sp, color: Colors.grey)), - ], - )), - SizedBox(width: 15.w), - Text('去升级'.tr, style: TextStyle(fontSize: 22.sp)), - Image( - width: 40.w, - height: 24.w, - image: const AssetImage('images/icon_right_black.png')) - ], + child: Obx( + () => + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('3天滚动储存'.tr, + style: TextStyle(fontSize: 24.sp)), + SizedBox(height: 10.h), + Text("${F + .navTitle}${"已为本设备免费提供3大滚动视频储存服务" + .tr}", + style: + TextStyle(fontSize: 22.sp, color: Colors + .grey)), + ], + )), + SizedBox(width: 15.w), + Text('去升级'.tr, style: TextStyle(fontSize: 22.sp)), + Image( + width: 40.w, + height: 24.w, + image: const AssetImage( + 'images/icon_right_black.png')) + ], + ), + SizedBox( + height: 16.h, + ), + Text( + '云存服务状态:${_handlerValidityPeriodStatsText()}', + style: TextStyle( + fontSize: 24.sp, + ), + ), + SizedBox( + height: 8.h, + ), + Visibility( + visible: state.validityPeriodInfo.value != null && + state.validityPeriodInfo.value?.status == 1, + child: Text( + '过期时间:${state.validityPeriodInfo.value + ?.validityPeriodEnd}', + style: TextStyle( + fontSize: 24.sp, + ), + ), + ), + SizedBox( + height: 8.h, + ), + Visibility( + visible: state.validityPeriodInfo.value != null && + state.validityPeriodInfo.value?.status == 1, + child: Text( + '滚动存储天数:${state.validityPeriodInfo.value?.rollingStorageDays} 天', + style: TextStyle( + fontSize: 24.sp, + ), + ), + ), + SizedBox( + height: 8.h, + ), + Visibility( + visible: state.validityPeriodInfo.value != null && + state.validityPeriodInfo.value?.status == 1, + child: Text( + '剩余天数:${state.validityPeriodInfo.value + ?.remainingDays} 天', + style: TextStyle( + fontSize: 24.sp, + ), + ), + ), + ], + ), ), ), ); } + _handlerValidityPeriodStatsText() { + if (state.validityPeriodInfo.value == null) { + return ''; + } + if (state.validityPeriodInfo.value?.status == 1) { + return '已开通'.tr; + } else if (state.validityPeriodInfo.value?.status == 2) { + return '已过期'.tr; + } else { + return '未开通'.tr; + } + } + // 本地顶部 Widget localTip() { return GestureDetector( @@ -244,7 +329,7 @@ class _VideoLogPageState extends State { // height: 130.h, margin: EdgeInsets.all(15.w), padding: - EdgeInsets.only(left: 20.w, top: 30.w, bottom: 30.w, right: 10.w), + EdgeInsets.only(left: 20.w, top: 30.w, bottom: 30.w, right: 10.w), decoration: BoxDecoration( color: const Color(0xFFF6F7F8), borderRadius: BorderRadius.circular(20.h)), @@ -252,15 +337,15 @@ class _VideoLogPageState extends State { children: [ Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // SizedBox(height: 20.h), - Text('下载列表'.tr, style: TextStyle(fontSize: 24.sp)), - SizedBox(height: 15.h), - Text('暂无下载内容'.tr, - style: TextStyle(fontSize: 22.sp, color: Colors.grey)), - ], - )), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox(height: 20.h), + Text('下载列表'.tr, style: TextStyle(fontSize: 24.sp)), + SizedBox(height: 15.h), + Text('暂无下载内容'.tr, + style: TextStyle(fontSize: 22.sp, color: Colors.grey)), + ], + )), SizedBox(width: 15.w), // Text("去升级", style: TextStyle(fontSize: 24.sp)), Image( @@ -334,7 +419,7 @@ class _VideoLogPageState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - //横轴元素个数 + //横轴元素个数 crossAxisCount: 3, //纵轴间距 mainAxisSpacing: 15.w, @@ -356,7 +441,7 @@ class _VideoLogPageState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - //横轴元素个数 + //横轴元素个数 crossAxisCount: 3, //纵轴间距 mainAxisSpacing: 15.w, @@ -384,9 +469,10 @@ class _VideoLogPageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => FullScreenImagePage( - imageUrl: recordData.imagesUrl!, - ), + builder: (context) => + FullScreenImagePage( + imageUrl: recordData.imagesUrl!, + ), ), ); } diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart index 8b2a7960..cfa0c232 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart @@ -1,6 +1,8 @@ import 'dart:typed_data'; import 'package:get/get.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; class VideoLogState { @@ -17,4 +19,8 @@ class VideoLogState { getLockId.value = map['lockId']; } } + +// 声明响应式变量 + final validityPeriodInfo = Rx(null); + RxString cloudStorageWebViewUrl = ''.obs; } diff --git a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart index 45afb135..a936dd64 100755 --- a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart +++ b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart @@ -16,6 +16,5 @@ class VideoLogDetailState { if (map['videoDataList'] != null) { videoLogList.value = map['videoDataList']; } - print('object'); } } diff --git a/lib/mine/addLock/lock_voice_setting/lock_voice_setting_logic.dart b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_logic.dart new file mode 100644 index 00000000..fd30419f --- /dev/null +++ b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_logic.dart @@ -0,0 +1,406 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart'; +import 'package:flutter/cupertino.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/appRouters.dart'; +import 'package:star_lock/app_settings/app_colors.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_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/main/lockDetail/lockDetail/passthrough_item.dart'; +import 'package:star_lock/mine/addLock/lock_voice_setting/lock_voice_setting_state.dart'; +import 'package:star_lock/network/api_repository.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; +import 'package:star_lock/tools/eventBusEventManage.dart'; +import 'package:star_lock/tools/showTipView.dart'; +import 'package:star_lock/tools/storage.dart'; +import 'package:http/http.dart' as http; + +class LockVoiceSettingLogic extends BaseGetXController { + LockVoiceSettingState state = LockVoiceSettingState(); + StreamSubscription? _replySubscription; + bool _isThrottled = false; + + @override + void onInit() async { + super.onInit(); + _replySubscription = + EventBusManager().eventBus!.on().listen((Reply reply) async { + if (reply is GetDeviceModelReply) { + // 获取设备型号 + _handlerDeviceModelReply(reply); + } + if (reply is VoicePackageConfigureReply) { + // 语言包配置开始 + _handlerStartVoicePackageConfigure(reply); + } else if (reply is VoicePackageConfigureProcessReply) { + _handlerVoicePackageConfigureProcess(reply); + } else if (reply is VoicePackageConfigureConfirmationReply) { + handleVoiceConfigureThrottled(reply); + } + }); + await sendGetDeviceModelBleMessage(); + } + + void handleVoiceConfigureThrottled( + VoicePackageConfigureConfirmationReply reply, + ) { + if (_isThrottled) return; + + _isThrottled = true; + + // 执行你的逻辑 + _executeLogic(reply); + + // 设置节流时间(比如 1 秒) + Future.delayed(Duration(seconds: 1), () { + _isThrottled = false; + }); + } + + Future _executeLogic( + VoicePackageConfigureConfirmationReply reply) async { + final PassthroughItem item = + state.languages[state.selectPassthroughListIndex.value]; + final timbre = item.timbres[state.selectLanguageIndex.value]; + final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre( + data: { + 'lang': item.lang, + 'timbre': timbre.timbre, + }, + lockId: state.lockSetInfoData.value.lockId!, + ); + if (entity.errorCode!.codeIsSuccessful) { + showCupertinoDialog( + context: Get.context!, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text('语音设置'.tr), + content: Text('语音设置成功'.tr), + actions: [ + CupertinoDialogAction( + child: Text( + '取消'.tr, + style: TextStyle(color: AppColors.mainColor), + ), + onPressed: () { + Get.back(); + }, + ), + CupertinoDialogAction( + child: Text( + '返回主页'.tr, + style: TextStyle(color: AppColors.mainColor), + ), + onPressed: () { + state.lockSetInfoData.value.lockSettingInfo + ?.currentVoiceTimbre?.lang = item.lang; + state.lockSetInfoData.value.lockSettingInfo + ?.currentVoiceTimbre?.timbre = timbre.timbre; + + eventBus.fire(PassCurrentLockInformationEvent( + state.lockSetInfoData.value)); + Get.offAllNamed(Routers.starLockMain); + }, + ), + ], + ); + }, + ); + } + dismissEasyLoading(); + } + + void saveSpeechLanguageSettings() async { + // 如果已经开始发送中则不处理保存点击事件 + if (state.progress.value > 0) { + return; + } + final oldTimbre = + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre; + final oldLang = + state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang; + + EasyLoading.showProgress(state.progress.value, status: '正在发送数据'); + final PassthroughItem item = + state.languages[state.selectPassthroughListIndex.value]; + final timbre = item.timbres[state.selectLanguageIndex.value]; + debugPrint('选中的语音是:${timbre}'); + if ((oldLang != null && oldLang == item.lang) && + (oldTimbre != null && oldTimbre == timbre.timbre)) { + showToast('已设置为当前选择的语音包'.tr); + } + await downloadFile(timbre.timbrePackUrl); + } + + //下载语音包 + 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(); + // showBlueConnetctToast(); + } + }); + } + + void _handlerVoicePackageConfigureProcess( + VoicePackageConfigureProcessReply reply) { + final int status = reply.data[2]; + switch (status) { + case 0x00: + cancelBlueConnetctToastTimer(); + state.voiceSubcontractingIndex++; + _sendNextPackage(); + break; + default: + showToast('发送分包数据不成功'.tr); + break; + } + } + + // 开始配置语音包 + 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 _handlerDeviceModelReply(GetDeviceModelReply reply) async { + final int status = reply.data[2]; + switch (status) { + case 0x00: + //成功 + cancelBlueConnetctToastTimer(); + // 3. 解析DeviceModel(40字节,索引3~42) + int startIndex = 3; + int length = 40; + List deviceModelBytes = + reply.data.sublist(startIndex, startIndex + length); + + String rawData = String.fromCharCodes(deviceModelBytes); + int firstNullIndex = rawData.indexOf('\u0000'); + String deviceModel = rawData.substring(0, firstNullIndex); + print('获取到 DeviceModel (原始): $deviceModel'); + state.deviceModel.value = deviceModel; + await initList(); + break; + default: + showToast('获取设备型号失败'.tr); + break; + } + } + + /// 获取列表 + initList() async { + showEasyLoading(); + try { + final PassthroughListResponse entity = await ApiRepository.to + .getPassthroughList(data: {'deviceType': state.deviceModel.value}); + if (entity.errorCode!.codeIsSuccessful) { + state.languages.value = entity.data!!; + final oldTimbre = state + .lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre; + final oldLang = state + .lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang; + + for (int index = 0; index < state.languages.value.length; index++) { + final element = state.languages.value[index]; + final timbres = element.timbres; + for (int i = 0; i < timbres.length; i++) { + final timbre = timbres[i].timbre; + if ((oldLang != null && oldLang == element.lang) && + (oldTimbre != null && oldTimbre == timbre)) { + state.selectPassthroughListIndex.value = index; + state.selectLanguageIndex.value = i; + } + } + } + } + } catch (e) { + debugPrint('获取语音包出现错误:$e'); + } finally { + dismissEasyLoading(); + } + } + + // 发送获取型号蓝牙命令 + 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 _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 _sendNextPackage() { + 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)}%'); + _sendLanguageFileBleMessage( + index: state.voiceSubcontractingIndex, + data: packageData, + ); + } + + _sendLanguageFileBleMessage({required int index, required Uint8List data}) { + BlueManage().blueSendData(BlueManage().connectDeviceName, + (BluetoothConnectionState deviceConnectionState) async { + if (deviceConnectionState == BluetoothConnectionState.connected) { + BlueManage().writeCharacteristicWithResponse( + VoicePackageConfigureProcess( + index: index, + size: data.length, + data: data, + ).packageData(), + ); + } else if (deviceConnectionState == + BluetoothConnectionState.disconnected) { + dismissEasyLoading(); + cancelBlueConnetctToastTimer(); + // showBlueConnetctToast(); + } + }); + } + + @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(); + } +} diff --git a/lib/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart new file mode 100644 index 00000000..543f0903 --- /dev/null +++ b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:get/get_utils/get_utils.dart'; +import 'package:star_lock/appRouters.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; +import 'package:star_lock/mine/addLock/lock_voice_setting/lock_voice_setting_logic.dart'; +import 'package:star_lock/mine/addLock/lock_voice_setting/lock_voice_setting_state.dart'; +import 'package:star_lock/tools/EasyRefreshTool.dart'; +import 'package:star_lock/tools/titleAppBar.dart'; + +class LockVoiceSetting extends StatefulWidget { + const LockVoiceSetting(); + + @override + State createState() => _LockVoiceSettingState(); +} + +class _LockVoiceSettingState extends State { + final LockVoiceSettingLogic logic = Get.put(LockVoiceSettingLogic()); + final LockVoiceSettingState state = Get.find().state; + + @override + Widget build(BuildContext context) { + return EasyRefreshTool( + onRefresh: () { + logic.sendGetDeviceModelBleMessage(); + }, + child: Scaffold( + backgroundColor: AppColors.mainBackgroundColor, + appBar: TitleAppBar( + barTitle: '锁语音包设置'.tr, + haveBack: false, + haveOtherLeftWidget: true, + leftWidget: TextButton( + onPressed: () { + Get.offAllNamed(Routers.starLockMain); + }, + child: Text( + '跳过'.tr, + style: TextStyle( + color: Colors.white, + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + backgroundColor: AppColors.mainColor, + actionsList: [ + TextButton( + onPressed: logic.saveSpeechLanguageSettings, + child: Text( + '保存'.tr, + style: TextStyle( + color: Colors.white, + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + body: _buildBody(), + ), + ); + } + + Widget _buildBody() { + return Obx( + () => SingleChildScrollView( + child: Column( + children: _buildList(), + ), + ), + ); + } + + List _buildList() { + final languages = state.languages; + return List.generate( + languages.length, + (index) => _buildItem(languages[index], index), + ); + } + + _buildItem(PassthroughItem language, int index) { + final timbres = language.timbres; + final isSelected = state.selectPassthroughListIndex == index; + return ExpansionTile( + title: Text( + PassthroughLangHelper.getLangText(language.lang), + style: TextStyle( + fontSize: 24.sp, + fontWeight: isSelected ? FontWeight.bold : null, + ), + ), + onExpansionChanged: (bool expanded) {}, + initiallyExpanded: false, + backgroundColor: Colors.white, + collapsedBackgroundColor: Colors.white, + expandedCrossAxisAlignment: CrossAxisAlignment.center, + expandedAlignment: Alignment.center, + shape: InputBorder.none, + maintainState: true, + // 去除展开状态下的边框 + collapsedShape: InputBorder.none, + // 去除折叠状态下的边框 + childrenPadding: EdgeInsets.only(left: 12.w), + children: List.generate( + timbres.length, + (int languageIndex) => ListTile( + title: Text( + timbres[languageIndex].name, + style: TextStyle( + fontSize: 22.sp, + fontWeight: + state.selectLanguageIndex == languageIndex && isSelected + ? FontWeight.bold + : null, + ), + ), + trailing: state.selectLanguageIndex == languageIndex && isSelected + ? Icon( + Icons.check_circle, + color: AppColors.mainColor, + ) // 仅当选中时显示图标 + : null, // 默认不显示 + onTap: () { + // 更新选中的语音包 + state.selectLanguageIndex.value = languageIndex; + // 更新选中的语言 + state.selectPassthroughListIndex.value = index; + }, // 默认图标, // 右侧的图标 + ), + ), + ); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + if (EasyLoading.isShow) { + EasyLoading.dismiss(animation: true); + } + } +} diff --git a/lib/mine/addLock/lock_voice_setting/lock_voice_setting_state.dart b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_state.dart new file mode 100644 index 00000000..e70c6b2a --- /dev/null +++ b/lib/mine/addLock/lock_voice_setting/lock_voice_setting_state.dart @@ -0,0 +1,47 @@ +import 'dart:typed_data'; + +import 'package:get/get.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; +import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; + +class LockVoiceSettingState { + LockVoiceSettingState() { + final map = Get.arguments; + lockSetInfoData.value = map['lockSetInfoData']; + + lockBasicInfo.value = lockSetInfoData.value.lockBasicInfo!; + } + + Rx lockSetInfoData = LockSetInfoData().obs; + Rx lockBasicInfo = LockBasicInfo().obs; + + + + // 选中的语音包列表下标 + RxInt selectPassthroughListIndex = 0.obs; + + // 选中的语音下标 + RxInt selectLanguageIndex = 0.obs; + + final RxList languages = [].obs; + + Map languageSpeechDeviceTypeMapping = {0: '2403'}; + + RxBool otaUpdateIng = false.obs; + RxDouble otaProgress = 0.00.obs; + RxString deviceModel = '2403'.obs; + Uint8List? data; + + // 语音包最大大小 + int voiceSubcontractingSize = 256; + + // 总数据包数量 + int voiceSubcontractingCount = 0; + + // 数据包序列号 + int voiceSubcontractingIndex = 0; + + // 分包发送进度(0.0~1.0) + RxDouble progress = 0.0.obs; + +} diff --git a/lib/mine/addLock/saveLock/saveLock_logic.dart b/lib/mine/addLock/saveLock/saveLock_logic.dart index 6facc933..e5de0c33 100755 --- a/lib/mine/addLock/saveLock/saveLock_logic.dart +++ b/lib/mine/addLock/saveLock/saveLock_logic.dart @@ -486,7 +486,6 @@ class SaveLockLogic extends BaseGetXController { // } void backAction() async { - // BlueManage().disconnect(); // 查询锁设置信息 @@ -502,13 +501,19 @@ class SaveLockLogic extends BaseGetXController { 'lockSetInfoData': state.lockSetInfoData.value, 'pageName': 'saveLock' }); + } else if (state.lockSetInfoData.value.lockFeature?.languageSpeech == 1) { + Get.toNamed(Routers.lockVoiceSettingPage, arguments: { + 'lockSetInfoData': state.lockSetInfoData.value, + 'pageName': 'saveLock' + }); } else { - eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true)); + eventBus.fire(RefreshLockListInfoDataEvent( + clearScanDevices: true, isUnShowLoading: true)); Future.delayed(const Duration(seconds: 1), () { // Get.close(state.isFromMap == 1 // ? (CommonDataManage().seletLockType == 0 ? 4 : 5) // : (CommonDataManage().seletLockType == 0 ? 5 : 6)); - Get.until((route) => route.isFirst); + Get.until((route) => route.isFirst); }); //刚刚配对完,需要对开锁页锁死 2 秒 Future.delayed(const Duration(milliseconds: 200), () { @@ -520,7 +525,8 @@ class SaveLockLogic extends BaseGetXController { }); } } else { - eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true)); + eventBus.fire(RefreshLockListInfoDataEvent( + clearScanDevices: true, isUnShowLoading: true)); Future.delayed(const Duration(seconds: 1), () { // Get.close(state.isFromMap == 1 // ? (CommonDataManage().seletLockType == 0 ? 4 : 5) diff --git a/lib/mine/addLock/selectLockType/selectLockType_page.dart b/lib/mine/addLock/selectLockType/selectLockType_page.dart index 3688d0ae..fdc9ad60 100755 --- a/lib/mine/addLock/selectLockType/selectLockType_page.dart +++ b/lib/mine/addLock/selectLockType/selectLockType_page.dart @@ -84,17 +84,17 @@ class _SelectLockTypePageState extends State arguments: {'getLockType': 1}); }), // if (!F.isLite) - lockTypeItem('images/lockType/lockType_NFCLock.png', 'NFC无源锁'.tr, () { - CommonDataManage().seletLockType = 2; - // Navigator.pushNamed(context, Routers.addLockPage); - logic.getNearByLimits(); - }), + lockTypeItem('images/lockType/lockType_NFCLock.png', 'NFC无源锁'.tr, () { + CommonDataManage().seletLockType = 2; + // Navigator.pushNamed(context, Routers.addLockPage); + logic.getNearByLimits(); + }), // if (!F.isLite) - lockTypeItem('images/lockType/lockType_padlock.png', '挂锁'.tr, () { - CommonDataManage().seletLockType = 3; - // Navigator.pushNamed(context, Routers.addLockPage); - logic.getNearByLimits(); - }), + lockTypeItem('images/lockType/lockType_padlock.png', '挂锁'.tr, () { + CommonDataManage().seletLockType = 3; + // Navigator.pushNamed(context, Routers.addLockPage); + logic.getNearByLimits(); + }), lockTypeItem('images/lockType/lockType_safeLock.png', '保险箱锁'.tr, () { CommonDataManage().seletLockType = 4; Navigator.pushNamed(context, Routers.addLockPage, @@ -223,9 +223,16 @@ class _SelectLockTypePageState extends State mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(lockTypeTitle, - style: TextStyle( - fontSize: 22.sp, color: AppColors.blackColor)), + Text( + lockTypeTitle, + maxLines: 2, // 最大行数 + overflow: TextOverflow.ellipsis, // 超出显示省略号 + softWrap: true, // 自动换行 + style: TextStyle( + fontSize: 22.sp, + color: AppColors.blackColor, + ), + ), ], ), ), diff --git a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart index 15a4fe3c..b27a5066 100755 --- a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart +++ b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart @@ -32,12 +32,16 @@ class Data { String? vipBuyUrl; String? cloudauthBuyUrl; String? shopList; + String? cloudStorage; + String? valueAddServiceLimitFree; Data( {this.smsBuyUrl, this.emailBuyUrl, this.vipBuyUrl, this.cloudauthBuyUrl, + this.cloudStorage, + this.valueAddServiceLimitFree, this.shopList}); Data.fromJson(Map json) { @@ -46,6 +50,8 @@ class Data { vipBuyUrl = json['vip_buy_url']; cloudauthBuyUrl = json['cloudauth_buy_url']; shopList = json['shopList']; + cloudStorage = json['cloud_storage']; + valueAddServiceLimitFree = json['value_add_service_limit_free']; } Map toJson() { @@ -55,6 +61,8 @@ class Data { data['vip_buy_url'] = vipBuyUrl; data['cloudauth_buy_url'] = cloudauthBuyUrl; data['shopList'] = shopList; + data['cloud_storage'] = cloudStorage; + data['value_add_service_limit_free'] = valueAddServiceLimitFree; return data; } } diff --git a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_logic.dart b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_logic.dart index ab7d83f8..1b43d893 100755 --- a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_logic.dart +++ b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_logic.dart @@ -40,6 +40,9 @@ class AdvancedFeaturesWebLogic extends BaseGetXController { } else if (state.webBuyType.value == XSConstantMacro.webBuyTypeShop) { state.webBuyUrl.value = entity.data!.shopList!; state.webBuyTitle.value = '商城购买'.tr; + }else if (state.webBuyType.value == XSConstantMacro.webBuyTypeCloudStorage) { + state.webBuyUrl.value = entity.data!.cloudStorage!; + state.webBuyTitle.value = '云存购买'.tr; } state.webBuyView.setNavigationDelegate( diff --git a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_state.dart b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_state.dart index c478181a..86b95a3e 100755 --- a/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_state.dart +++ b/lib/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_state.dart @@ -19,6 +19,8 @@ class AdvancedFeaturesWebState { webBuyTitle.value = '邮件购买'.tr; } else if (webBuyType.value == XSConstantMacro.webBuyTypeShop) { webBuyTitle.value = '商城购买'.tr; + }else if (webBuyType.value == XSConstantMacro.webBuyTypeCloudStorage) { + webBuyTitle.value = '云存购买'.tr; } } } diff --git a/lib/mine/valueAddedServices/cloudStorage/cloud_storage_logic.dart b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_logic.dart new file mode 100644 index 00000000..0f37573e --- /dev/null +++ b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_logic.dart @@ -0,0 +1,6 @@ +import 'package:star_lock/mine/valueAddedServices/cloudStorage/cloud_storage_state.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; + +class CloudStorageLogic extends BaseGetXController { + CloudStorageState state = CloudStorageState(); +} diff --git a/lib/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart new file mode 100644 index 00000000..0ed05870 --- /dev/null +++ b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart @@ -0,0 +1,150 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/mine/valueAddedServices/cloudStorage/cloud_storage_logic.dart'; +import 'package:star_lock/mine/valueAddedServices/cloudStorage/cloud_storage_state.dart'; +import 'package:star_lock/tools/titleAppBar.dart'; + +class CloudStoragePage extends StatefulWidget { + const CloudStoragePage(); + + @override + State createState() => _CloudStoragePageState(); +} + +class _CloudStoragePageState extends State { + final CloudStorageLogic logic = Get.put(CloudStorageLogic()); + final CloudStorageState state = Get.find().state; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: TitleAppBar( + barTitle: '云存储购买'.tr, + haveBack: true, + iconColor: Colors.black, + titleColor: Colors.black, + backgroundColor: Colors.white, + ), + body: SafeArea( + child: _buildBody(), + ), + ); + } + + Widget _buildBody() { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.center, + colors: [Color(0xFFC5F0E7), Color(0xFFF7F7F7)], + ), + ), + child: Column( + children: [ + // 购买套餐选项卡 + _buildPurchasePackage() + ], + ), + ); + } + + Widget _buildPurchasePackage() { + return Container( + width: 1.sw, + margin: EdgeInsets.symmetric( + vertical: 12.h, + horizontal: 14.w, + ), + decoration: BoxDecoration( + color: const Color(0xFFF7F7F7), + borderRadius: BorderRadiusDirectional.all( + Radius.circular(16.r), + ), + ), + child: Column( + children: [ + _buildTabs(), + _buildTabContent(), + ], + ), + ); + } + + Widget _buildTabs() { + return Obx( + () => Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: List.generate( + state.tabs.length, + (int index) => _buildTabItem(index), + ), + ), + ); + } + + Widget _buildTabItem(int index) { + return Expanded( + child: GestureDetector( + onTap: () { + state.selectedIndex.value = index; + }, + child: Container( + height: 68.h, + decoration: BoxDecoration( + color: _isSelectTabBgColor(index), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(16.r), + topEnd: Radius.circular(16.r), + ), + ), + child: Center( + child: Text( + state.tabs[index], + style: _isSelectTabTitle(index), + ), + ), + ), + ), + ); + } + + TextStyle _isSelectTabTitle(int index) { + if (state.selectedIndex.value == index) { + return TextStyle( + color: const Color(0xFF040404), + fontWeight: FontWeight.w600, + fontSize: 28.sp, + ); + } + return TextStyle( + color: const Color(0xFF8F8F8F), + fontSize: 28.sp, + ); + } + + Color _isSelectTabBgColor(int index) { + if (state.selectedIndex.value == index) { + return Colors.white; + } + return const Color(0xFFF7F7F7); + } + + Widget _buildTabContent() { + return Container( + width: 1.sw, + padding: EdgeInsets.symmetric( + vertical: 12.h, + horizontal: 14.w, + ), + decoration: BoxDecoration( + color: Colors.white, + ), + child: Text('asd'), + ); + } +} diff --git a/lib/mine/valueAddedServices/cloudStorage/cloud_storage_state.dart b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_state.dart new file mode 100644 index 00000000..93879f8a --- /dev/null +++ b/lib/mine/valueAddedServices/cloudStorage/cloud_storage_state.dart @@ -0,0 +1,13 @@ +import 'package:get/get.dart'; + +class CloudStorageState { + // 选中的索引 + RxInt selectedIndex = 0.obs; + + // 选项卡标题 + final List tabs = ['7天滚动存储', '30天滚动存储']; + final List> tabContent = [ + {'title': '连续包月', 'price': '1', 'price2': '188', 'discount': '立省188元'}, + {'title': '连续包月', 'price': '1', 'price2': '188', 'discount': '立省188元'} + ]; +} diff --git a/lib/network/api.dart b/lib/network/api.dart index db9ccf33..412af193 100755 --- a/lib/network/api.dart +++ b/lib/network/api.dart @@ -265,6 +265,8 @@ abstract class Api { final String getServiceUserPackageURL = '/v2/service/getUserPackage'; //获取增值服务用户余量包 + final String getValidityPeriodInfoURL = '/passthrough'; + //获取云存储服务信息 final String getlockCloudStorageListURL = '/lockCloudStorage/list'; //获取云存列表 final String deleteLockCloudStorageURL = '/lockCloudStorage/delete'; //删除云存 @@ -298,4 +300,9 @@ abstract class Api { '/SL-A-1.0/peer/nslookup'; // 星图--解析对端信息 final String bindUserStarchartURL = '/userStarchart/bindUserStarchart'; // 绑定星图配置 + final String getPassthroughListURL = '/passthrough'; // 获取语音列表 + final String updateCurrentVoiceTimbre = + '/lockSetting/updateLockSetting'; // 设置语音包 + final String reportBuyRequestURL = + '/service/reportBuyRequest'; // 上报增值服务购买请求 } diff --git a/lib/network/api_provider.dart b/lib/network/api_provider.dart index 01aabb37..0325b302 100755 --- a/lib/network/api_provider.dart +++ b/lib/network/api_provider.dart @@ -2400,6 +2400,22 @@ class ApiProvider extends BaseProvider { 'searchStr': searchStr })); + // 获取云存列表 + Future getValidityPeriodInfo( + int lockId, { + String request_method = 'POST', + String request_uri = '/api/v1/cloudStorage/getStorageServiceInfo', + required Map post_args, + }) => + post( + getValidityPeriodInfoURL.toUrl, + jsonEncode({ + 'lockId': lockId, + 'request_method': request_method, + 'request_uri': request_uri, + 'post_args': post_args, + })); + // 获取云存列表 Future getLockCloudStorageList(int lockId) => post(getlockCloudStorageListURL.toUrl, jsonEncode({'lockId': lockId})); @@ -2773,6 +2789,55 @@ class ApiProvider extends BaseProvider { isShowNetworkErrorMsg: false, isShowErrMsg: false, isUnShowLoading: true); + + /// 获取设备配网信息 + Future getPassthroughList( + String requestMethod, + String requestUri, + Map data, + ) => + post( + getPassthroughListURL.toUrl, + jsonEncode({ + 'request_method': requestMethod, + 'request_uri': requestUri, + 'post_args': data, + }), + isShowNetworkErrorMsg: false, + isShowErrMsg: false, + isUnShowLoading: true); + + /// 设置语音包 + Future settingCurrentVoiceTimbre( + int lockId, + Map data, + ) => + post( + updateCurrentVoiceTimbre.toUrl, + jsonEncode({ + 'lockId': lockId, + 'currentVoiceTimbre': data, + }), + isShowNetworkErrorMsg: false, + isShowErrMsg: false, + isUnShowLoading: true); + + /// 设置语音包 + Future reportBuyRequest( + int lockId, + String type, + ) => + post( + reportBuyRequestURL.toUrl, + jsonEncode({ + 'lockId': lockId, + 'type': type, + }), + isShowNetworkErrorMsg: false, + isShowErrMsg: false, + isUnShowLoading: true); + + } extension ExtensionString on String { diff --git a/lib/network/api_repository.dart b/lib/network/api_repository.dart index 86d3c604..f3e2587a 100755 --- a/lib/network/api_repository.dart +++ b/lib/network/api_repository.dart @@ -13,6 +13,7 @@ import 'package:star_lock/main/lockDetail/electronicKey/sendEmailNotification/se import 'package:star_lock/main/lockDetail/face/addFace/addFace_entity.dart'; import 'package:star_lock/main/lockDetail/fingerprint/fingerprintList/fingerprint_entity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/device_network_info.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; import 'package:star_lock/main/lockDetail/lockSet/basicInformation/basicInformation/KeyDetailEntity.dart'; import 'package:star_lock/main/lockDetail/lockSet/lockEscalation/updateLockInfo_entity.dart'; import 'package:star_lock/main/lockDetail/lockSet/lockEscalation/version_entity.dart'; @@ -2435,6 +2436,30 @@ class ApiRepository { return CoerceFingerprintListEntity.fromJson(res.body); } + // 获取云存储服务信息 + Future getValidityPeriodInfo({ + required int lockId, + }) async { + Map post_args = Map.of({'lockId': lockId}); + final res = await apiProvider.getValidityPeriodInfo( + lockId, + post_args: post_args, + ); + return ValidityPeriodResponse.fromJson(res.body); + } + + // 上报增值服务购买请求 + Future uploadReportBuyRequest({ + required int lockId, + String type = 'cloud_storage', + }) async { + final res = await apiProvider.reportBuyRequest( + lockId, + type, + ); + return LoginEntity.fromJson(res.body); + } + // 获取云存列表 Future getLockCloudStorageList({required int lockId}) async { final res = await apiProvider.getLockCloudStorageList(lockId); @@ -2770,4 +2795,30 @@ class ApiRepository { ); return DeviceNetwork.fromJson(res.body); } + + // 获取语音列表 + Future getPassthroughList({ + String requestMethod = 'POST', + String requestUri = '/api/v1/voice/packs', + required Map data, + }) async { + final res = await apiProvider.getPassthroughList( + requestMethod, + requestUri, + data, + ); + return PassthroughListResponse.fromJson(res.body); + } + + // 设置语音 + Future settingCurrentVoiceTimbre({ + required int lockId, + required Map data, + }) async { + final res = await apiProvider.settingCurrentVoiceTimbre( + lockId, + data, + ); + return LoginEntity.fromJson(res.body); + } } diff --git a/lib/talk/starChart/command/message_command.dart b/lib/talk/starChart/command/message_command.dart index 06dcb314..25e3eb18 100644 --- a/lib/talk/starChart/command/message_command.dart +++ b/lib/talk/starChart/command/message_command.dart @@ -277,7 +277,6 @@ class MessageCommand { int? SpTotal, int? SpIndex, }) { - final payload = talkData.writeToBuffer(); ScpMessage message = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart index 17651ddf..b58ebe56 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart @@ -630,6 +630,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 重置期望数据 StartChartManage().reSetDefaultTalkExpect(); + StartChartManage().stopTalkExpectMessageTimer(); VideoDecodePlugin.releaseDecoder(); // 取消批处理定时器 diff --git a/lib/tools/storage.dart b/lib/tools/storage.dart index 8f20526a..7918a0f5 100755 --- a/lib/tools/storage.dart +++ b/lib/tools/storage.dart @@ -40,6 +40,7 @@ const String lockNetWorkInfo = 'lockNetWorkInfo'; //锁板配网信息 const String appVersionHistoryUrl = 'appVersionHistoryUrl'; //是否同意隐私协议弹窗 const String voipToken = 'voipToken'; //是否同意隐私协议弹窗 +const String deviceModel = 'deviceModel'; //设备型号 class Storage { factory Storage() => _instance;