Merge branch 'develop_sky_liyi' into 'develop_sky'
Develop sky liyi See merge request StarlockTeam/app-starlock!213
This commit is contained in:
commit
768f7fd38f
@ -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"
|
||||
}
|
||||
|
||||
@ -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 @@
|
||||
"请确认后再继续": "请确认后再继续",
|
||||
"需要相机权限": "需要相机权限",
|
||||
"此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行",
|
||||
"网关添加成功": "网关添加成功"
|
||||
"网关添加成功": "网关添加成功",
|
||||
"语音包设置": "语音包设置",
|
||||
}
|
||||
|
||||
@ -1166,5 +1166,6 @@
|
||||
"请确认后再继续": "请确认后再继续",
|
||||
"需要相机权限": "需要相机权限",
|
||||
"一键登录": "一键登录",
|
||||
"网关添加成功": "网关添加成功"
|
||||
"网关添加成功": "网关添加成功",
|
||||
"语音包设置": "语音包设置"
|
||||
}
|
||||
|
||||
@ -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<dynamic>(
|
||||
name: Routers.valueAddedCloudStoragePage,
|
||||
page: () => const CloudStoragePage(),
|
||||
),
|
||||
GetPage<dynamic>(
|
||||
name: Routers.customSMSTemplateListPage,
|
||||
page: () => const CustomSMSTemplateListPage(),
|
||||
@ -1024,6 +1037,9 @@ abstract class AppRouters {
|
||||
name: Routers.faceUnlockPage, page: () => const FaceUnlockPage()),
|
||||
GetPage<dynamic>(
|
||||
name: Routers.motorPowerPage, page: () => const MotorPowerPage()),
|
||||
GetPage<dynamic>(
|
||||
name: Routers.speechLanguageSettingsPage,
|
||||
page: () => const SpeechLanguageSettingsPage()),
|
||||
GetPage<dynamic>(
|
||||
name: Routers.openDoorDirectionPage,
|
||||
page: () => const OpenDoorDirectionPage()),
|
||||
@ -1201,6 +1217,9 @@ abstract class AppRouters {
|
||||
GetPage<dynamic>(
|
||||
name: Routers.permissionGuidancePage,
|
||||
page: () => PermissionGuidancePage()),
|
||||
GetPage<dynamic>(
|
||||
name: Routers.lockVoiceSettingPage,
|
||||
page: () => LockVoiceSetting()),
|
||||
// 插件播放页面
|
||||
// GetPage<dynamic>(name: Routers.h264View, page: () => H264WebView()), // webview播放页面
|
||||
];
|
||||
|
||||
48
lib/blue/io_protocol/io_getDeviceModel.dart
Normal file
48
lib/blue/io_protocol/io_getDeviceModel.dart
Normal file
@ -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<int> messageDetail() {
|
||||
List<int> 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<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
status = dataDetail[2];
|
||||
data = dataDetail;
|
||||
}
|
||||
}
|
||||
163
lib/blue/io_protocol/io_voicePackageConfigure.dart
Normal file
163
lib/blue/io_protocol/io_voicePackageConfigure.dart
Normal file
@ -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<int>? signKey;
|
||||
List<int>? privateKey;
|
||||
List<int>? 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<int> messageDetail() {
|
||||
List<int> data = <int>[];
|
||||
List<int> ebcData = <int>[];
|
||||
|
||||
// 指令类型
|
||||
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(<int>[0, 1]); //先默认是 01
|
||||
|
||||
|
||||
//fwSize 4
|
||||
final ByteData bytes = ByteData(4); // 创建一个长度为4的字节数据
|
||||
bytes.setInt32(0, fwSize!);
|
||||
final List<int> 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<int> authCodeData = <int>[];
|
||||
|
||||
//KeyID
|
||||
authCodeData.addAll(utf8.encode(keyID!));
|
||||
|
||||
//UserID
|
||||
authCodeData.addAll(utf8.encode(userID!));
|
||||
|
||||
//token 4 首次请求 Token 填 0,如果锁需要鉴权操作者身份,则会分配动态口令并在应答消息中返回,二次请求时带上。
|
||||
authCodeData.addAll(token ?? <int>[]);
|
||||
|
||||
authCodeData.addAll(signKey ?? <int>[]);
|
||||
|
||||
// 把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<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
data = dataDetail;
|
||||
token = data.sublist(2, 6);
|
||||
status = data[6];
|
||||
errorWithStstus(status);
|
||||
}
|
||||
|
||||
List<int> token = <int>[];
|
||||
}
|
||||
73
lib/blue/io_protocol/io_voicePackageConfigureProcess.dart
Normal file
73
lib/blue/io_protocol/io_voicePackageConfigureProcess.dart
Normal file
@ -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<int>? data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VoicePackageConfigureProcess{index: $index, size: $size, data: $data}';
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> messageDetail() {
|
||||
final List<int> 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<int> indexList = indexBytes.buffer.asUint8List();
|
||||
data.addAll(indexList);
|
||||
|
||||
//size 2
|
||||
final ByteData bytes = ByteData(2); // 创建一个长度为4的字节数据
|
||||
bytes.setInt16(0, size!);
|
||||
final List<int> byteList = bytes.buffer.asUint8List();
|
||||
data.addAll(byteList);
|
||||
|
||||
data.addAll(this.data!);
|
||||
|
||||
printLog(data);
|
||||
//不加密
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class VoicePackageConfigureProcessReply extends Reply {
|
||||
VoicePackageConfigureProcessReply.parseData(
|
||||
CommandType commandType, List<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
data = dataDetail;
|
||||
status = data[2];
|
||||
errorWithStstus(status);
|
||||
}
|
||||
}
|
||||
|
||||
class VoicePackageConfigureConfirmationReply extends Reply {
|
||||
VoicePackageConfigureConfirmationReply.parseData(
|
||||
CommandType commandType, List<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
data = dataDetail;
|
||||
status = data[2];
|
||||
errorWithStstus(status);
|
||||
}
|
||||
}
|
||||
@ -288,16 +288,27 @@ String asciiString(List<int> codeUnits) {
|
||||
}
|
||||
|
||||
String utf8String(List<int> codeUnits) {
|
||||
codeUnits.reversed;
|
||||
final List<int> uniqueList = <int>[];
|
||||
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<int> 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<int> codeUnits) {
|
||||
// codeUnits.reversed;
|
||||
// final List<int> uniqueList = <int>[];
|
||||
// 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<int>? list1, List<int>? list2}) {
|
||||
if (list1!.length != list2!.length) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
{
|
||||
// 子命令类型
|
||||
|
||||
@ -90,6 +90,7 @@ class XSConstantMacro {
|
||||
static int webBuyTypeVip = 3; //VIP购买
|
||||
static int webBuyTypeAuth = 4; //实名购买
|
||||
static int webBuyTypeShop = 5; //商城购买
|
||||
static int webBuyTypeCloudStorage = 6; //云存购买
|
||||
|
||||
//设备类型信息
|
||||
Future<Map<String, dynamic>> getDeviceInfoData() async {
|
||||
|
||||
@ -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,12 +840,30 @@ class LockDetailLogic extends BaseGetXController {
|
||||
state.DetailLockInfo = eventBus
|
||||
.on<PassCurrentLockInformationEvent>()
|
||||
.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,
|
||||
@ -853,11 +871,17 @@ class LockDetailLogic extends BaseGetXController {
|
||||
recordMode: 0,
|
||||
recordStartTime: 0,
|
||||
recordTime: '0',
|
||||
detectionDistance: '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();
|
||||
}
|
||||
});
|
||||
|
||||
281
lib/main/lockDetail/lockDetail/passthrough_item.dart
Normal file
281
lib/main/lockDetail/lockDetail/passthrough_item.dart
Normal file
@ -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<String, dynamic> json) {
|
||||
return TimbreItem(
|
||||
timbre: json['timbre'] ?? '',
|
||||
name: json['name'] ?? '',
|
||||
timbrePackUrl: json['timbrePackUrl'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<TimbreItem> timbres;
|
||||
|
||||
PassthroughItem({
|
||||
required this.lang,
|
||||
required this.timbres,
|
||||
});
|
||||
|
||||
factory PassthroughItem.fromJson(Map<String, dynamic> json) {
|
||||
var timbresJson = json['timbres'] as List<dynamic>?;
|
||||
List<TimbreItem> timbresList = timbresJson != null
|
||||
? timbresJson
|
||||
.map((e) => TimbreItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: <TimbreItem>[];
|
||||
return PassthroughItem(
|
||||
lang: json['lang'] ?? '',
|
||||
timbres: timbresList,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<PassthroughItem>? 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<String, dynamic>))
|
||||
.toList();
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
errorMsg = json['errorMsg'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
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<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return {
|
||||
'lockId': lockId,
|
||||
'status': status,
|
||||
'validityPeriodStart': validityPeriodStart,
|
||||
'validityPeriodEnd': validityPeriodEnd,
|
||||
'rollingStorageDays': rollingStorageDays,
|
||||
'remainingDays': remainingDays,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -165,7 +165,8 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
wifiName: wifiName ?? '',
|
||||
secretKey: secretKey,
|
||||
deviceMac: deviceMac ?? '',
|
||||
networkMac: networkMac ?? '');
|
||||
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));
|
||||
|
||||
@ -158,6 +158,7 @@ class LockFeature {
|
||||
this.isElectronicAntiLock,
|
||||
this.isVisualDoorBellCode,
|
||||
this.isDoubleLockLinkage,
|
||||
this.languageSpeech,
|
||||
});
|
||||
|
||||
LockFeature.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@ -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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@ -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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['lang'] = lang;
|
||||
data['timbre'] = timbre;
|
||||
return data;
|
||||
}
|
||||
|
||||
// 从 JSON 数据构造对象
|
||||
factory CurrentVoiceTimbre.fromJson(Map<String, dynamic> json) {
|
||||
return CurrentVoiceTimbre(
|
||||
timbre: json['timbre'],
|
||||
lang: json['lang'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<int> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -526,6 +526,20 @@ class _LockSetPageState extends State<LockSetPage>
|
||||
'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: <String, LockSetInfoData>{
|
||||
'lockSetInfoData': state.lockSetInfoData.value
|
||||
});
|
||||
})),
|
||||
// 蓝牙广播(关闭则不能使用蓝牙主动开锁)
|
||||
/* 2024-01-12 会议确定去掉“蓝牙广播” by DaisyWu
|
||||
Obx(() => Visibility(
|
||||
|
||||
@ -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<Reply>? _replySubscription;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().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<void> downloadFile(String url) async {
|
||||
final http.Response response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
state.data = response.bodyBytes;
|
||||
sendFileToDevice(response.bodyBytes, <int>[0, 0, 0, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
sendFileToDevice(Uint8List data, List<int> token) {
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState deviceConnectionState) async {
|
||||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||
final List<String>? privateKey =
|
||||
await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList =
|
||||
changeStringListToIntList(privateKey!);
|
||||
final List<String>? signKey =
|
||||
await Storage.getStringList(saveBlueSignKey);
|
||||
final List<int> signKeyDataList = changeStringListToIntList(signKey!);
|
||||
final String uid = await Storage.getUid() ?? '';
|
||||
final String md5Str = md5.convert(data).toString().toUpperCase();
|
||||
BlueManage().writeCharacteristicWithResponse(
|
||||
VoicePackageConfigure(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
userID: uid,
|
||||
keyID: BlueManage().connectDeviceName,
|
||||
platform: 0,
|
||||
product: 0,
|
||||
fwSize: data.length,
|
||||
fwMD5: md5Str,
|
||||
needAuthor: 1,
|
||||
token: token,
|
||||
signKey: signKeyDataList,
|
||||
privateKey: getPrivateKeyList)
|
||||
.packageData(),
|
||||
);
|
||||
} else if (deviceConnectionState ==
|
||||
BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 发送获取型号蓝牙命令
|
||||
sendGetDeviceModelBleMessage() {
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState deviceConnectionState) async {
|
||||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||
BlueManage().writeCharacteristicWithResponse(
|
||||
GetDeviceModelCommand(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
).packageData(),
|
||||
);
|
||||
} else if (deviceConnectionState ==
|
||||
BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
showBlueConnetctToast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _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<int> 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<int> token = reply.data.sublist(2, 6);
|
||||
print('收到token:$token');
|
||||
if (state.data != null) {
|
||||
sendFileToDevice(state.data!, token);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
showToast('获取设备型号失败'.tr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _startSendLanguageFile() {
|
||||
if (state.data == null) return;
|
||||
state.voiceSubcontractingIndex = 0;
|
||||
state.voiceSubcontractingCount =
|
||||
(state.data!.length + state.voiceSubcontractingSize - 1) ~/
|
||||
state.voiceSubcontractingSize;
|
||||
state.progress.value = 0.0; // 开始前重置进度
|
||||
_sendNextPackage();
|
||||
}
|
||||
|
||||
void _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();
|
||||
}
|
||||
}
|
||||
@ -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<SpeechLanguageSettingsPage> createState() =>
|
||||
_SpeechLanguageSettingsPageState();
|
||||
}
|
||||
|
||||
class _SpeechLanguageSettingsPageState
|
||||
extends State<SpeechLanguageSettingsPage> {
|
||||
final SpeechLanguageSettingsLogic logic =
|
||||
Get.put(SpeechLanguageSettingsLogic());
|
||||
final SpeechLanguageSettingsState state =
|
||||
Get.find<SpeechLanguageSettingsLogic>().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<Widget> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 = LockSetInfoData().obs;
|
||||
|
||||
// 选中的语音包列表下标
|
||||
RxInt selectPassthroughListIndex = 0.obs;
|
||||
|
||||
// 选中的语音下标
|
||||
RxInt selectLanguageIndex = 0.obs;
|
||||
|
||||
final RxList<PassthroughItem> languages = <PassthroughItem>[].obs;
|
||||
|
||||
Map<int, String> 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;
|
||||
|
||||
|
||||
}
|
||||
@ -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: <String, int>{
|
||||
'webBuyType': XSConstantMacro.webBuyTypeCloudStorage,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,9 @@ class VideoLogPage extends StatefulWidget {
|
||||
|
||||
class _VideoLogPageState extends State<VideoLogPage> {
|
||||
final VideoLogLogic logic = Get.put(VideoLogLogic());
|
||||
final VideoLogState state = Get.find<VideoLogLogic>().state;
|
||||
final VideoLogState state = Get
|
||||
.find<VideoLogLogic>()
|
||||
.state;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -54,7 +56,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
// title加编辑按钮
|
||||
editVideoTip(),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
() =>
|
||||
Visibility(
|
||||
visible: !state.isNavLocal.value,
|
||||
child: state.videoLogList.length > 0
|
||||
? Expanded(
|
||||
@ -83,7 +86,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
),
|
||||
// 本地顶部
|
||||
Obx(
|
||||
() => Visibility(
|
||||
() =>
|
||||
Visibility(
|
||||
visible: state.isNavLocal.value,
|
||||
child: state.lockVideoList.length > 0
|
||||
? Expanded(
|
||||
@ -157,7 +161,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
// logic.clearDownloads();
|
||||
});
|
||||
},
|
||||
child: Obx(() => Text('云存'.tr,
|
||||
child: Obx(() =>
|
||||
Text('云存'.tr,
|
||||
style: state.isNavLocal.value == true
|
||||
? TextStyle(
|
||||
color: Colors.grey,
|
||||
@ -175,7 +180,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
});
|
||||
},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
() =>
|
||||
Text(
|
||||
'已下载'.tr,
|
||||
style: state.isNavLocal.value == true
|
||||
? TextStyle(
|
||||
@ -197,8 +203,10 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
// 云存顶部视频
|
||||
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,
|
||||
@ -208,16 +216,26 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF6F7F8),
|
||||
borderRadius: BorderRadius.circular(20.h)),
|
||||
child: Row(
|
||||
child: Obx(
|
||||
() =>
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text('3天滚动储存'.tr, style: TextStyle(fontSize: 24.sp)),
|
||||
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)),
|
||||
Text("${F
|
||||
.navTitle}${"已为本设备免费提供3大滚动视频储存服务"
|
||||
.tr}",
|
||||
style:
|
||||
TextStyle(fontSize: 22.sp, color: Colors
|
||||
.grey)),
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 15.w),
|
||||
@ -225,13 +243,80 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
Image(
|
||||
width: 40.w,
|
||||
height: 24.w,
|
||||
image: const AssetImage('images/icon_right_black.png'))
|
||||
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(
|
||||
@ -384,7 +469,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FullScreenImagePage(
|
||||
builder: (context) =>
|
||||
FullScreenImagePage(
|
||||
imageUrl: recordData.imagesUrl!,
|
||||
),
|
||||
),
|
||||
|
||||
@ -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<ValidityPeriod?>(null);
|
||||
RxString cloudStorageWebViewUrl = ''.obs;
|
||||
}
|
||||
|
||||
@ -16,6 +16,5 @@ class VideoLogDetailState {
|
||||
if (map['videoDataList'] != null) {
|
||||
videoLogList.value = map['videoDataList'];
|
||||
}
|
||||
print('object');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Reply>? _replySubscription;
|
||||
bool _isThrottled = false;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().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<void> _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: <Widget>[
|
||||
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<void> downloadFile(String url) async {
|
||||
final http.Response response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
state.data = response.bodyBytes;
|
||||
sendFileToDevice(response.bodyBytes, <int>[0, 0, 0, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
sendFileToDevice(Uint8List data, List<int> token) {
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState deviceConnectionState) async {
|
||||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||
final List<String>? privateKey =
|
||||
await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList =
|
||||
changeStringListToIntList(privateKey!);
|
||||
final List<String>? signKey =
|
||||
await Storage.getStringList(saveBlueSignKey);
|
||||
final List<int> signKeyDataList = changeStringListToIntList(signKey!);
|
||||
final String uid = await Storage.getUid() ?? '';
|
||||
final String md5Str = md5.convert(data).toString().toUpperCase();
|
||||
BlueManage().writeCharacteristicWithResponse(
|
||||
VoicePackageConfigure(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
userID: uid,
|
||||
keyID: BlueManage().connectDeviceName,
|
||||
platform: 0,
|
||||
product: 0,
|
||||
fwSize: data.length,
|
||||
fwMD5: md5Str,
|
||||
needAuthor: 1,
|
||||
token: token,
|
||||
signKey: signKeyDataList,
|
||||
privateKey: getPrivateKeyList)
|
||||
.packageData(),
|
||||
);
|
||||
} else if (deviceConnectionState ==
|
||||
BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
// 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<int> 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<int> 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();
|
||||
}
|
||||
}
|
||||
150
lib/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart
Normal file
150
lib/mine/addLock/lock_voice_setting/lock_voice_setting_page.dart
Normal file
@ -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<LockVoiceSetting> createState() => _LockVoiceSettingState();
|
||||
}
|
||||
|
||||
class _LockVoiceSettingState extends State<LockVoiceSetting> {
|
||||
final LockVoiceSettingLogic logic = Get.put(LockVoiceSettingLogic());
|
||||
final LockVoiceSettingState state = Get.find<LockVoiceSettingLogic>().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<Widget> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 = LockSetInfoData().obs;
|
||||
Rx<LockBasicInfo> lockBasicInfo = LockBasicInfo().obs;
|
||||
|
||||
|
||||
|
||||
// 选中的语音包列表下标
|
||||
RxInt selectPassthroughListIndex = 0.obs;
|
||||
|
||||
// 选中的语音下标
|
||||
RxInt selectLanguageIndex = 0.obs;
|
||||
|
||||
final RxList<PassthroughItem> languages = <PassthroughItem>[].obs;
|
||||
|
||||
Map<int, String> 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;
|
||||
|
||||
}
|
||||
@ -486,7 +486,6 @@ class SaveLockLogic extends BaseGetXController {
|
||||
// }
|
||||
|
||||
void backAction() async {
|
||||
|
||||
// BlueManage().disconnect();
|
||||
|
||||
// 查询锁设置信息
|
||||
@ -502,8 +501,14 @@ 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<void>.delayed(const Duration(seconds: 1), () {
|
||||
// Get.close(state.isFromMap == 1
|
||||
// ? (CommonDataManage().seletLockType == 0 ? 4 : 5)
|
||||
@ -520,7 +525,8 @@ class SaveLockLogic extends BaseGetXController {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true));
|
||||
eventBus.fire(RefreshLockListInfoDataEvent(
|
||||
clearScanDevices: true, isUnShowLoading: true));
|
||||
Future<void>.delayed(const Duration(seconds: 1), () {
|
||||
// Get.close(state.isFromMap == 1
|
||||
// ? (CommonDataManage().seletLockType == 0 ? 4 : 5)
|
||||
|
||||
@ -223,9 +223,16 @@ class _SelectLockTypePageState extends State<SelectLockTypePage>
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(lockTypeTitle,
|
||||
Text(
|
||||
lockTypeTitle,
|
||||
maxLines: 2, // 最大行数
|
||||
overflow: TextOverflow.ellipsis, // 超出显示省略号
|
||||
softWrap: true, // 自动换行
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp, color: AppColors.blackColor)),
|
||||
fontSize: 22.sp,
|
||||
color: AppColors.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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<String, dynamic> 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<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
150
lib/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart
Normal file
150
lib/mine/valueAddedServices/cloudStorage/cloud_storage_page.dart
Normal file
@ -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<CloudStoragePage> createState() => _CloudStoragePageState();
|
||||
}
|
||||
|
||||
class _CloudStoragePageState extends State<CloudStoragePage> {
|
||||
final CloudStorageLogic logic = Get.put(CloudStorageLogic());
|
||||
final CloudStorageState state = Get.find<CloudStorageLogic>().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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class CloudStorageState {
|
||||
// 选中的索引
|
||||
RxInt selectedIndex = 0.obs;
|
||||
|
||||
// 选项卡标题
|
||||
final List<String> tabs = ['7天滚动存储', '30天滚动存储'];
|
||||
final List<Map<String, dynamic>> tabContent = [
|
||||
{'title': '连续包月', 'price': '1', 'price2': '188', 'discount': '立省188元'},
|
||||
{'title': '连续包月', 'price': '1', 'price2': '188', 'discount': '立省188元'}
|
||||
];
|
||||
}
|
||||
@ -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'; // 上报增值服务购买请求
|
||||
}
|
||||
|
||||
@ -2400,6 +2400,22 @@ class ApiProvider extends BaseProvider {
|
||||
'searchStr': searchStr
|
||||
}));
|
||||
|
||||
// 获取云存列表
|
||||
Future<Response> getValidityPeriodInfo(
|
||||
int lockId, {
|
||||
String request_method = 'POST',
|
||||
String request_uri = '/api/v1/cloudStorage/getStorageServiceInfo',
|
||||
required Map<String, dynamic> post_args,
|
||||
}) =>
|
||||
post(
|
||||
getValidityPeriodInfoURL.toUrl,
|
||||
jsonEncode({
|
||||
'lockId': lockId,
|
||||
'request_method': request_method,
|
||||
'request_uri': request_uri,
|
||||
'post_args': post_args,
|
||||
}));
|
||||
|
||||
// 获取云存列表
|
||||
Future<Response> getLockCloudStorageList(int lockId) =>
|
||||
post(getlockCloudStorageListURL.toUrl, jsonEncode({'lockId': lockId}));
|
||||
@ -2773,6 +2789,55 @@ class ApiProvider extends BaseProvider {
|
||||
isShowNetworkErrorMsg: false,
|
||||
isShowErrMsg: false,
|
||||
isUnShowLoading: true);
|
||||
|
||||
/// 获取设备配网信息
|
||||
Future<Response> getPassthroughList(
|
||||
String requestMethod,
|
||||
String requestUri,
|
||||
Map<String, String> data,
|
||||
) =>
|
||||
post(
|
||||
getPassthroughListURL.toUrl,
|
||||
jsonEncode({
|
||||
'request_method': requestMethod,
|
||||
'request_uri': requestUri,
|
||||
'post_args': data,
|
||||
}),
|
||||
isShowNetworkErrorMsg: false,
|
||||
isShowErrMsg: false,
|
||||
isUnShowLoading: true);
|
||||
|
||||
/// 设置语音包
|
||||
Future<Response> settingCurrentVoiceTimbre(
|
||||
int lockId,
|
||||
Map<String, String> data,
|
||||
) =>
|
||||
post(
|
||||
updateCurrentVoiceTimbre.toUrl,
|
||||
jsonEncode({
|
||||
'lockId': lockId,
|
||||
'currentVoiceTimbre': data,
|
||||
}),
|
||||
isShowNetworkErrorMsg: false,
|
||||
isShowErrMsg: false,
|
||||
isUnShowLoading: true);
|
||||
|
||||
/// 设置语音包
|
||||
Future<Response> reportBuyRequest(
|
||||
int lockId,
|
||||
String type,
|
||||
) =>
|
||||
post(
|
||||
reportBuyRequestURL.toUrl,
|
||||
jsonEncode({
|
||||
'lockId': lockId,
|
||||
'type': type,
|
||||
}),
|
||||
isShowNetworkErrorMsg: false,
|
||||
isShowErrMsg: false,
|
||||
isUnShowLoading: true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension ExtensionString on String {
|
||||
|
||||
@ -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<ValidityPeriodResponse> getValidityPeriodInfo({
|
||||
required int lockId,
|
||||
}) async {
|
||||
Map<String, dynamic> post_args = Map.of({'lockId': lockId});
|
||||
final res = await apiProvider.getValidityPeriodInfo(
|
||||
lockId,
|
||||
post_args: post_args,
|
||||
);
|
||||
return ValidityPeriodResponse.fromJson(res.body);
|
||||
}
|
||||
|
||||
// 上报增值服务购买请求
|
||||
Future<LoginEntity> uploadReportBuyRequest({
|
||||
required int lockId,
|
||||
String type = 'cloud_storage',
|
||||
}) async {
|
||||
final res = await apiProvider.reportBuyRequest(
|
||||
lockId,
|
||||
type,
|
||||
);
|
||||
return LoginEntity.fromJson(res.body);
|
||||
}
|
||||
|
||||
// 获取云存列表
|
||||
Future<VideoLogEntity> getLockCloudStorageList({required int lockId}) async {
|
||||
final res = await apiProvider.getLockCloudStorageList(lockId);
|
||||
@ -2770,4 +2795,30 @@ class ApiRepository {
|
||||
);
|
||||
return DeviceNetwork.fromJson(res.body);
|
||||
}
|
||||
|
||||
// 获取语音列表
|
||||
Future<PassthroughListResponse> getPassthroughList({
|
||||
String requestMethod = 'POST',
|
||||
String requestUri = '/api/v1/voice/packs',
|
||||
required Map<String, String> data,
|
||||
}) async {
|
||||
final res = await apiProvider.getPassthroughList(
|
||||
requestMethod,
|
||||
requestUri,
|
||||
data,
|
||||
);
|
||||
return PassthroughListResponse.fromJson(res.body);
|
||||
}
|
||||
|
||||
// 设置语音
|
||||
Future<LoginEntity> settingCurrentVoiceTimbre({
|
||||
required int lockId,
|
||||
required Map<String, String> data,
|
||||
}) async {
|
||||
final res = await apiProvider.settingCurrentVoiceTimbre(
|
||||
lockId,
|
||||
data,
|
||||
);
|
||||
return LoginEntity.fromJson(res.body);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,7 +277,6 @@ class MessageCommand {
|
||||
int? SpTotal,
|
||||
int? SpIndex,
|
||||
}) {
|
||||
|
||||
final payload = talkData.writeToBuffer();
|
||||
ScpMessage message = ScpMessage(
|
||||
ProtocolFlag: ProtocolFlagConstant.scp01,
|
||||
|
||||
@ -630,6 +630,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
// 重置期望数据
|
||||
StartChartManage().reSetDefaultTalkExpect();
|
||||
StartChartManage().stopTalkExpectMessageTimer();
|
||||
VideoDecodePlugin.releaseDecoder();
|
||||
|
||||
// 取消批处理定时器
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user