Compare commits
12 Commits
develop_sk
...
develop_sk
| Author | SHA1 | Date | |
|---|---|---|---|
| 513607e1ef | |||
| 6cc00ca348 | |||
| 21aa3adf7b | |||
| f6e900aaad | |||
| 41aea1d1bf | |||
| 5ed7e4013e | |||
| a4892633a3 | |||
| f73943785f | |||
| baeb3c139a | |||
| e1011688de | |||
| 4f1b1dd02f | |||
| ae176ffb6c |
@ -46,7 +46,7 @@ class BlueManage {
|
||||
StreamSubscription<BluetoothConnectionState>? _connectionStateSubscription;
|
||||
|
||||
StreamSubscription<int>? _mtuSubscription;
|
||||
int? _mtuSize = 20;
|
||||
int? _mtuSize = 30;
|
||||
|
||||
// 当前连接设备的名字
|
||||
String connectDeviceName = '';
|
||||
@ -119,8 +119,7 @@ class BlueManage {
|
||||
_connectionStateSubscription?.cancel();
|
||||
_connectionStateSubscription = null;
|
||||
|
||||
_connectionStateSubscription =
|
||||
bluetoothConnectDevice!.connectionState.listen((BluetoothConnectionState state) async {
|
||||
_connectionStateSubscription = bluetoothConnectDevice!.connectionState.listen((BluetoothConnectionState state) async {
|
||||
bluetoothConnectionState = state;
|
||||
AppLog.log('蓝牙连接回调状态:$state');
|
||||
});
|
||||
@ -159,26 +158,20 @@ class BlueManage {
|
||||
// AppLog.log('startScanSingle 蓝牙状态 系统蓝牙状态:$_adapterState 蓝牙连接状态:$bluetoothConnectionState');
|
||||
if (_adapterState == BluetoothAdapterState.on) {
|
||||
try {
|
||||
BuglyTool.uploadException(
|
||||
message: '开始指定设备名称的扫描蓝牙设备', detail: '调用方法是:startScanSingle 指定设备名称是:$deviceName', upload: false);
|
||||
BuglyTool.uploadException(message: '开始指定设备名称的扫描蓝牙设备', detail: '调用方法是:startScanSingle 指定设备名称是:$deviceName', upload: false);
|
||||
//android 扫描比较慢,取样只要 3 分之一
|
||||
final int divisor = Platform.isAndroid ? 3 : 1;
|
||||
FlutterBluePlus.startScan(
|
||||
continuousDivisor: divisor,
|
||||
continuousUpdates: true,
|
||||
withKeywords: <String>[deviceName],
|
||||
timeout: Duration(seconds: timeout));
|
||||
continuousDivisor: divisor, continuousUpdates: true, withKeywords: <String>[deviceName], timeout: Duration(seconds: timeout));
|
||||
final Completer<dynamic> completer = Completer<dynamic>();
|
||||
final StreamSubscription<List<ScanResult>> subscription =
|
||||
FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
||||
final bool isExit = results.any((ScanResult element) =>
|
||||
(element.device.platformName == deviceName) || (element.advertisementData.advName == deviceName));
|
||||
final StreamSubscription<List<ScanResult>> subscription = FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
||||
final bool isExit = results
|
||||
.any((ScanResult element) => (element.device.platformName == deviceName) || (element.advertisementData.advName == deviceName));
|
||||
final int milliseconds = DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch;
|
||||
AppLog.log('扫描到的设备数:${results.length} 是否查找到 $isExit 以查找$milliseconds毫秒');
|
||||
BuglyTool.uploadException(
|
||||
message: '指定设备名称的扫描蓝牙设备 监听扫描结果',
|
||||
detail:
|
||||
'startScanSingle$deviceName 监听扫描结果 是否查找到 $isExit 以查找$milliseconds毫秒 扫描到的设备数:${results.length} results:$results',
|
||||
detail: 'startScanSingle$deviceName 监听扫描结果 是否查找到 $isExit 以查找$milliseconds毫秒 扫描到的设备数:${results.length} results:$results',
|
||||
upload: false);
|
||||
if (isExit) {
|
||||
for (final ScanResult scanResult in results) {
|
||||
@ -215,8 +208,7 @@ class BlueManage {
|
||||
completer.complete();
|
||||
}
|
||||
}, onError: (e) {
|
||||
BuglyTool.uploadException(
|
||||
message: '指定设备名称的扫描蓝牙设备 监听扫描结果失败', detail: '打印失败问题 e:${e.toString()}', upload: false);
|
||||
BuglyTool.uploadException(message: '指定设备名称的扫描蓝牙设备 监听扫描结果失败', detail: '打印失败问题 e:${e.toString()}', upload: false);
|
||||
AppLog.log('扫描失败:$e');
|
||||
});
|
||||
FlutterBluePlus.cancelWhenScanComplete(subscription);
|
||||
@ -224,8 +216,7 @@ class BlueManage {
|
||||
scanDevicesCallBack(scanDevices);
|
||||
subscription.cancel();
|
||||
} catch (e) {
|
||||
BuglyTool.uploadException(
|
||||
message: '指定设备名称的扫描蓝牙设备 内部逻辑整形失败', detail: 'tartScanSingle内部逻辑整形失败 e:${e.toString()}', upload: false);
|
||||
BuglyTool.uploadException(message: '指定设备名称的扫描蓝牙设备 内部逻辑整形失败', detail: 'tartScanSingle内部逻辑整形失败 e:${e.toString()}', upload: false);
|
||||
AppLog.log('扫描失败');
|
||||
}
|
||||
} else {
|
||||
@ -242,16 +233,14 @@ class BlueManage {
|
||||
}
|
||||
|
||||
/// 开始扫描蓝牙设备
|
||||
Future<void> startScan(int timeout, DeviceType deviceType, ScanDevicesCallBack scanDevicesCallBack,
|
||||
{List<Guid>? idList}) async {
|
||||
Future<void> startScan(int timeout, DeviceType deviceType, ScanDevicesCallBack scanDevicesCallBack, {List<Guid>? idList}) async {
|
||||
FlutterBluePlus.isSupported.then((bool isAvailable) async {
|
||||
if (isAvailable) {
|
||||
AppLog.log('startScan 蓝牙状态 系统蓝牙状态:$_adapterState 蓝牙连接状态:$bluetoothConnectionState');
|
||||
if (_adapterState == BluetoothAdapterState.on) {
|
||||
try {
|
||||
FlutterBluePlus.startScan(timeout: Duration(seconds: timeout));
|
||||
final StreamSubscription<List<ScanResult>> subscription =
|
||||
FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
||||
final StreamSubscription<List<ScanResult>> subscription = FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
||||
scanDevices.clear();
|
||||
for (final ScanResult scanResult in results) {
|
||||
if (scanResult.advertisementData.serviceUuids.isNotEmpty) {
|
||||
@ -420,16 +409,13 @@ class BlueManage {
|
||||
} else {
|
||||
BuglyTool.uploadException(
|
||||
message: '点击按钮 蓝牙已经连接 下一步扫描连接蓝牙',
|
||||
detail:
|
||||
'blueSendData 直接回调状态 蓝牙连接状态bluetoothConnectionState:$bluetoothConnectionState deviceName:$deviceName',
|
||||
detail: 'blueSendData 直接回调状态 蓝牙连接状态bluetoothConnectionState:$bluetoothConnectionState deviceName:$deviceName',
|
||||
upload: false);
|
||||
stateCallBack(bluetoothConnectionState!);
|
||||
}
|
||||
} else {
|
||||
BuglyTool.uploadException(
|
||||
message: '点击按钮 蓝牙未打开',
|
||||
detail: 'blueSendData 蓝牙未打开--_adapterState:${BluetoothAdapterState.on} deviceName:$deviceName',
|
||||
upload: false);
|
||||
message: '点击按钮 蓝牙未打开', detail: 'blueSendData 蓝牙未打开--_adapterState:${BluetoothAdapterState.on} deviceName:$deviceName', upload: false);
|
||||
try {
|
||||
stateCallBack(BluetoothConnectionState.disconnected);
|
||||
openBlue();
|
||||
@ -442,8 +428,7 @@ class BlueManage {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BuglyTool.uploadException(
|
||||
message: '点击按钮 蓝牙状态不可用', detail: 'blueSendData 蓝牙状态不可用--isAvailable:$isAvailable', upload: false);
|
||||
BuglyTool.uploadException(message: '点击按钮 蓝牙状态不可用', detail: 'blueSendData 蓝牙状态不可用--isAvailable:$isAvailable', upload: false);
|
||||
stateCallBack(BluetoothConnectionState.disconnected);
|
||||
AppLog.log('开始扫描 蓝牙不可用,不能进行蓝牙操作');
|
||||
}
|
||||
@ -451,8 +436,7 @@ class BlueManage {
|
||||
}
|
||||
|
||||
/// 连接
|
||||
Future<void> _connect(String deviceName, ConnectStateCallBack connectStateCallBack,
|
||||
{bool isAddEquipment = false}) async {
|
||||
Future<void> _connect(String deviceName, ConnectStateCallBack connectStateCallBack, {bool isAddEquipment = false}) async {
|
||||
connectDeviceName = deviceName;
|
||||
// 当前已扫描到的缓存设备
|
||||
final List<ScanResult> devicesList = scanDevices;
|
||||
@ -533,8 +517,8 @@ class BlueManage {
|
||||
|
||||
//查找缓存里面是否有设备
|
||||
bool isExistScanDevices(String connectDeviceName) {
|
||||
final bool isExistDevice = scanDevices.any((ScanResult element) =>
|
||||
element.device.platformName == connectDeviceName || element.advertisementData.advName == connectDeviceName);
|
||||
final bool isExistDevice = scanDevices
|
||||
.any((ScanResult element) => element.device.platformName == connectDeviceName || element.advertisementData.advName == connectDeviceName);
|
||||
return isExistDevice;
|
||||
}
|
||||
|
||||
@ -545,11 +529,8 @@ class BlueManage {
|
||||
bool isAddEquipment = false, // 是否是添加设备之前
|
||||
bool isReconnect = true, // 是否是重连
|
||||
}) async {
|
||||
// 判断数组列表里面是否有这个设备
|
||||
// AppLog.log("devicesList:$devicesList");
|
||||
|
||||
final int knownDeviceIndex = devicesList.indexWhere(
|
||||
(ScanResult d) => (d.device.platformName == deviceName) || (d.advertisementData.advName == deviceName));
|
||||
final int knownDeviceIndex =
|
||||
devicesList.indexWhere((ScanResult d) => (d.device.platformName == deviceName) || (d.advertisementData.advName == deviceName));
|
||||
|
||||
ScanResult? scanResult; //使用局部变量防止出现缓存
|
||||
if (knownDeviceIndex >= 0) {
|
||||
@ -560,7 +541,6 @@ class BlueManage {
|
||||
|
||||
bluetoothConnectDevice = devicesList[knownDeviceIndex].device;
|
||||
scanResult = devicesList[knownDeviceIndex];
|
||||
// AppLog.log('bluetoothConnectDevice: $bluetoothConnectDevice scanResult:$scanResult');
|
||||
|
||||
_initGetMtuSubscription();
|
||||
_initListenConnectionState();
|
||||
@ -572,87 +552,13 @@ class BlueManage {
|
||||
upload: false);
|
||||
return;
|
||||
}
|
||||
AppLog.log('调用了停止扫描的方法');
|
||||
await stopScan();
|
||||
if (scanResult.advertisementData.serviceUuids[0].toString().length >= 5 &&
|
||||
(scanResult.advertisementData.serviceUuids[0].toString()[5] == '0') &&
|
||||
isAddEquipment == false) {
|
||||
// 添加这个判断是因为有些苹果设备或者安卓等性能比较好的设备时,添加完锁之后,锁板未改变为已添加状态之前,就进行了蓝牙连接,导致添加完锁就失败,这里进行了判断,如果第一次连接失败,就清除缓存重新扫描连接
|
||||
if (isReconnect == true) {
|
||||
AppLog.log('该锁已被重置, 重新发送扫描命令');
|
||||
|
||||
BuglyTool.uploadException(
|
||||
message: '该锁已被重置, 重新发送扫描命令startScanSingle 上传记录当前方法是:_connectDevice',
|
||||
detail:
|
||||
'添加这个判断是因为有些苹果设备或者安卓等性能比较好的设备时,添加完锁之后,锁板未改变为已添加状态之前,就进行了蓝牙连接,导致添加完锁就失败,这里进行了判断,如果第一次连接失败,就清除缓存重新扫描连接 该锁已被重置, 重新发送扫描命令 serviceUuids:${scanResult.advertisementData.serviceUuids[0].toString()}',
|
||||
upload: false);
|
||||
|
||||
scanDevices.clear();
|
||||
startScanSingle(deviceName, 15, (List<ScanResult> scanDevices) {
|
||||
_connectDevice(scanDevices, deviceName, connectStateCallBack,
|
||||
isAddEquipment: isAddEquipment, isReconnect: false);
|
||||
});
|
||||
} else {
|
||||
connectStateCallBack(BluetoothConnectionState.disconnected);
|
||||
if (!F.isSKY) {
|
||||
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
|
||||
}
|
||||
|
||||
scanDevices.clear();
|
||||
|
||||
BuglyTool.uploadException(
|
||||
message: '提示该锁已被重置, 回调断开连接, 清除缓存,上传记录当前方法是:_connectDevice',
|
||||
detail: 'isReconnect:$isReconnect serviceUuids:${scanResult.advertisementData.serviceUuids[0].toString()}',
|
||||
upload: false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (scanResult.advertisementData.serviceUuids[0].toString().length >= 30 &&
|
||||
(scanResult.advertisementData.serviceUuids[0].toString()[31] == '0') &&
|
||||
isAddEquipment == false) {
|
||||
// 添加这个判断是因为有些苹果设备或者安卓等性能比较好的设备时,添加完锁之后,锁板未改变为已添加状态之前,就进行了蓝牙连接,导致添加完锁就失败,这里进行了判断,如果第一次连接失败,就清除缓存重新扫描连接
|
||||
if (isReconnect == true) {
|
||||
AppLog.log('该锁已被重置, 重新发送扫描命令');
|
||||
|
||||
BuglyTool.uploadException(
|
||||
message: '该锁已被重置, 重新发送扫描命令startScanSingle 上传记录当前方法是:_connectDevice',
|
||||
detail:
|
||||
'添加这个判断是因为有些苹果设备或者安卓等性能比较好的设备时,添加完锁之后,锁板未改变为已添加状态之前,就进行了蓝牙连接,导致添加完锁就失败,这里进行了判断,如果第一次连接失败,就清除缓存重新扫描连接 该锁已被重置, 重新发送扫描命令 serviceUuids:${scanResult.advertisementData.serviceUuids[0].toString()}',
|
||||
upload: false);
|
||||
|
||||
scanDevices.clear();
|
||||
startScanSingle(deviceName, 15, (List<ScanResult> scanDevices) {
|
||||
_connectDevice(scanDevices, deviceName, connectStateCallBack,
|
||||
isAddEquipment: isAddEquipment, isReconnect: true);
|
||||
});
|
||||
} else {
|
||||
connectStateCallBack(BluetoothConnectionState.disconnected);
|
||||
if (!F.isSKY) {
|
||||
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
|
||||
}
|
||||
scanDevices.clear();
|
||||
|
||||
BuglyTool.uploadException(
|
||||
message: '提示该锁已被重置, 回调断开连接, 清除缓存,上传记录当前方法是:_connectDevice',
|
||||
detail: 'isReconnect:$isReconnect serviceUuids:${scanResult.advertisementData.serviceUuids[0].toString()}',
|
||||
upload: false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BuglyTool.uploadException(
|
||||
message: '从devicesList里面查到了设备 下一步连接设备 上传记录当前方法是:_connectDevice',
|
||||
detail:
|
||||
'devicesList:$devicesList scanResult:${scanResult.toString()} bluetoothConnectDevice:${bluetoothConnectDevice.toString()} connectDeviceMacAddress:$connectDeviceMacAddress',
|
||||
upload: false);
|
||||
|
||||
//连接设备
|
||||
await bluetoothDeviceConnect(bluetoothConnectDevice!, connectStateCallBack);
|
||||
}
|
||||
|
||||
//直接给蓝牙设备写入
|
||||
Future<void> doNotSearchBLE(String masAdds, ConnectStateCallBack connectStateCallBack,
|
||||
{bool isAddEquipment = false}) async {
|
||||
Future<void> doNotSearchBLE(String masAdds, ConnectStateCallBack connectStateCallBack, {bool isAddEquipment = false}) async {
|
||||
await FlutterBluePlus.stopScan();
|
||||
|
||||
if (bluetoothConnectDevice == null || bluetoothConnectDevice?.remoteId.str != masAdds) {
|
||||
@ -660,9 +566,7 @@ class BlueManage {
|
||||
_initGetMtuSubscription();
|
||||
_initListenConnectionState();
|
||||
BuglyTool.uploadException(
|
||||
message: '直接给蓝牙设备写入 上传记录当前方法是:doNotSearchBLE',
|
||||
detail: '直接给蓝牙设备写入 通过fromId方法创建一个BluetoothDevice masAdds:$masAdds',
|
||||
upload: false);
|
||||
message: '直接给蓝牙设备写入 上传记录当前方法是:doNotSearchBLE', detail: '直接给蓝牙设备写入 通过fromId方法创建一个BluetoothDevice masAdds:$masAdds', upload: false);
|
||||
} else {
|
||||
BuglyTool.uploadException(
|
||||
message: '直接给蓝牙设备写入 上传记录当前方法是:doNotSearchBLE',
|
||||
@ -747,9 +651,7 @@ class BlueManage {
|
||||
connectStateCallBack(bluetoothConnectionState!);
|
||||
AppLog.log('发现设备时失败 e:$e bluetoothConnectionState:$bluetoothConnectionState');
|
||||
BuglyTool.uploadException(
|
||||
message: '发现服务时失败',
|
||||
detail: '发现服务时报错原因e:$e bluetoothDeviceConnect:${bluetoothConnectDevice.toString()}',
|
||||
upload: false);
|
||||
message: '发现服务时失败', detail: '发现服务时报错原因e:$e bluetoothDeviceConnect:${bluetoothConnectDevice.toString()}', upload: false);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@ -819,12 +721,24 @@ class BlueManage {
|
||||
for (final BluetoothCharacteristic characteristic in service.characteristics) {
|
||||
if (characteristic.characteristicUuid == _characteristicIdWrite) {
|
||||
try {
|
||||
_initGetMtuSubscription();
|
||||
// 如果MTU还是默认值,尝试重新请求
|
||||
if ((_mtuSize == 23 || _mtuSize == 20) && bluetoothConnectDevice != null) {
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
await bluetoothConnectDevice!.requestMtu(512);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLog.log('重新请求MTU失败: $e');
|
||||
}
|
||||
}
|
||||
// 添加重试机制
|
||||
int retryCount = 0;
|
||||
const int maxRetries = 3;
|
||||
const int retryDelayMs = 500;
|
||||
|
||||
final List<int> valueList = value;
|
||||
AppLog.log('发送数据时当前的mtuSize是:${_mtuSize}');
|
||||
final List subData = splitList(valueList, _mtuSize!);
|
||||
|
||||
for (int i = 0; i < subData.length; i++) {
|
||||
|
||||
74
lib/blue/io_protocol/io_readRegisterKey.dart
Normal file
74
lib/blue/io_protocol/io_readRegisterKey.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../io_tool/io_tool.dart';
|
||||
import '../sm4Encipher/sm4.dart';
|
||||
import '../io_reply.dart';
|
||||
import '../io_sender.dart';
|
||||
import '../io_type.dart';
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
|
||||
/// 读取注册密钥
|
||||
class SenderReadRegisterKeyCommand extends SenderProtocol {
|
||||
SenderReadRegisterKeyCommand({
|
||||
this.lockID,
|
||||
this.token,
|
||||
this.needAuthor,
|
||||
this.publicKey,
|
||||
this.privateKey,
|
||||
}) : super(CommandType.readRegisterKey);
|
||||
|
||||
String? lockID;
|
||||
|
||||
List<int>? token;
|
||||
int? needAuthor;
|
||||
List<int>? publicKey;
|
||||
List<int>? privateKey;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SenderReadRegisterKeyCommand{ lockID: $lockID, token: $token, '
|
||||
'needAuthor: $needAuthor, publicKey: $publicKey, '
|
||||
'privateKey: $privateKey}';
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
if ((data.length % 16) != 0) {
|
||||
final int add = 16 - data.length % 16;
|
||||
for (int i = 0; i < add; i++) {
|
||||
data.add(0);
|
||||
}
|
||||
}
|
||||
|
||||
printLog(data);
|
||||
|
||||
ebcData = SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB);
|
||||
return ebcData;
|
||||
}
|
||||
}
|
||||
|
||||
class SenderReadRegisterKeyCommandReply extends Reply {
|
||||
SenderReadRegisterKeyCommandReply.parseData(CommandType commandType, List<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
data = dataDetail;
|
||||
|
||||
final int status = data[6];
|
||||
errorWithStstus(status);
|
||||
}
|
||||
}
|
||||
106
lib/blue/io_protocol/io_sendAuthorizationCode.dart
Normal file
106
lib/blue/io_protocol/io_sendAuthorizationCode.dart
Normal file
@ -0,0 +1,106 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../io_tool/io_tool.dart';
|
||||
import '../sm4Encipher/sm4.dart';
|
||||
import '../io_reply.dart';
|
||||
import '../io_sender.dart';
|
||||
import '../io_type.dart';
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
|
||||
/// 发送授权码
|
||||
class SenderAuthorizationCodeCommand extends SenderProtocol {
|
||||
SenderAuthorizationCodeCommand({
|
||||
this.lockID,
|
||||
this.uuid,
|
||||
this.key,
|
||||
this.mac,
|
||||
this.platform,
|
||||
this.utcTimeStamp,
|
||||
this.token,
|
||||
this.needAuthor,
|
||||
this.publicKey,
|
||||
this.privateKey,
|
||||
}) : super(CommandType.sendAuthorizationCode);
|
||||
|
||||
String? lockID;
|
||||
String? uuid;
|
||||
String? key;
|
||||
String? mac;
|
||||
int? platform; //0:锁通通;1:涂鸦智能
|
||||
int? utcTimeStamp;
|
||||
|
||||
List<int>? token;
|
||||
int? needAuthor;
|
||||
List<int>? publicKey;
|
||||
List<int>? privateKey;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SenderAuthorizationCodeCommand{ lockID: $lockID, token: $token, '
|
||||
'needAuthor: $needAuthor, publicKey: $publicKey, '
|
||||
'privateKey: $privateKey}';
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// uuid 40
|
||||
final int uuidLength = utf8.encode(uuid!).length;
|
||||
data.addAll(utf8.encode(uuid!));
|
||||
data = getFixedLengthList(data, 40 - uuidLength);
|
||||
|
||||
// key 40
|
||||
final int keyLength = utf8.encode(key!).length;
|
||||
data.addAll(utf8.encode(key!));
|
||||
data = getFixedLengthList(data, 40 - keyLength);
|
||||
|
||||
// mac 40
|
||||
final int macLength = utf8.encode(mac!).length;
|
||||
data.addAll(utf8.encode(mac!));
|
||||
data = getFixedLengthList(data, 40 - macLength);
|
||||
|
||||
data.add(platform!);
|
||||
|
||||
data.add((utcTimeStamp! & 0xff000000) >> 24);
|
||||
data.add((utcTimeStamp! & 0xff0000) >> 16);
|
||||
data.add((utcTimeStamp! & 0xff00) >> 8);
|
||||
data.add(utcTimeStamp! & 0xff);
|
||||
|
||||
if ((data.length % 16) != 0) {
|
||||
final int add = 16 - data.length % 16;
|
||||
for (int i = 0; i < add; i++) {
|
||||
data.add(0);
|
||||
}
|
||||
}
|
||||
|
||||
printLog(data);
|
||||
|
||||
ebcData = SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB);
|
||||
return ebcData;
|
||||
}
|
||||
}
|
||||
|
||||
class SenderAuthorizationCodeCommandReply extends Reply {
|
||||
SenderAuthorizationCodeCommandReply.parseData(CommandType commandType, List<int> dataDetail)
|
||||
: super.parseData(commandType, dataDetail) {
|
||||
data = dataDetail;
|
||||
|
||||
final int status = data[6];
|
||||
errorWithStstus(status);
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,8 @@ enum CommandType {
|
||||
gatewayGetWifiList, //网关获取附近的wifi列表 0x30F6
|
||||
gatewayGetWifiListResult, //网关获取附近的wifi列表结果 0x30F7
|
||||
gatewayGetStatus, //获取网关状态 0x30F8
|
||||
readRegisterKey, //读取注册密钥 0x30A7
|
||||
sendAuthorizationCode, //发送授权码 0x30A6
|
||||
|
||||
generalExtendedCommond, // 通用扩展指令 = 0x3030
|
||||
gecChangeAdministratorPassword, // 通用扩展指令子命令-修改管理员密码 = 2
|
||||
@ -245,6 +247,16 @@ extension ExtensionCommandType on CommandType {
|
||||
type = CommandType.gatewayGetStatus;
|
||||
}
|
||||
break;
|
||||
case 0x30A6:
|
||||
{
|
||||
type = CommandType.sendAuthorizationCode;
|
||||
}
|
||||
break;
|
||||
case 0x30A7:
|
||||
{
|
||||
type = CommandType.readRegisterKey;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
type = CommandType.readStarLockStatusInfo;
|
||||
@ -353,6 +365,12 @@ extension ExtensionCommandType on CommandType {
|
||||
case CommandType.setLockCurrentVoicePacket:
|
||||
type = 0x30A5;
|
||||
break;
|
||||
case CommandType.sendAuthorizationCode:
|
||||
type = 0x30A6;
|
||||
break;
|
||||
case CommandType.readRegisterKey:
|
||||
type = 0x30A7;
|
||||
break;
|
||||
default:
|
||||
type = 0x300A;
|
||||
break;
|
||||
@ -492,6 +510,12 @@ extension ExtensionCommandType on CommandType {
|
||||
case 0x30A5:
|
||||
t = '设置锁当前语音包';
|
||||
break;
|
||||
case 0x30A6:
|
||||
t = '发送授权码';
|
||||
break;
|
||||
case 0x30A7:
|
||||
t = '读取注册密钥';
|
||||
break;
|
||||
default:
|
||||
t = '读星锁状态信息';
|
||||
break;
|
||||
|
||||
@ -16,10 +16,12 @@ 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';
|
||||
import 'package:star_lock/blue/io_protocol/io_readRegisterKey.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsNoParameters.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsWithParameters.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_sendAuthorizationCode.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_setVoicePackageFinalResult.dart';
|
||||
@ -331,6 +333,18 @@ class CommandReciverManager {
|
||||
SetVoicePackageFinalResultReply.parseData(commandType, data);
|
||||
}
|
||||
break;
|
||||
case CommandType.readRegisterKey:
|
||||
{
|
||||
reply =
|
||||
SenderReadRegisterKeyCommandReply.parseData(commandType, data);
|
||||
}
|
||||
break;
|
||||
case CommandType.sendAuthorizationCode:
|
||||
{
|
||||
reply =
|
||||
SenderAuthorizationCodeCommandReply.parseData(commandType, data);
|
||||
}
|
||||
break;
|
||||
case CommandType.generalExtendedCommond:
|
||||
{
|
||||
// 子命令类型
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:star_lock/blue/entity/lock_user_no_list_entity.dart';
|
||||
@ -22,10 +21,10 @@ import 'io_tool/manager_event_bus.dart';
|
||||
import 'sender_data.dart';
|
||||
|
||||
class SenderBeforeDataManage {
|
||||
|
||||
factory SenderBeforeDataManage() => shareManager()!;
|
||||
|
||||
SenderBeforeDataManage._init();
|
||||
|
||||
static SenderBeforeDataManage? _manager;
|
||||
|
||||
static SenderBeforeDataManage? shareManager() {
|
||||
@ -42,6 +41,7 @@ class SenderBeforeDataManage {
|
||||
|
||||
// 监听设备返回的数据
|
||||
StreamSubscription<Reply>? _replySubscription;
|
||||
|
||||
// 是否是添加用户之前的调用
|
||||
bool isBeforeAddUser = true;
|
||||
|
||||
@ -146,10 +146,8 @@ class SenderBeforeDataManage {
|
||||
|
||||
//获取清除用户列表指令
|
||||
Future<List<int>> getCleanUpUsers({List<int>? tokenList}) async {
|
||||
final LockUserNoListEntity entity = await ApiRepository.to
|
||||
.getLockUserNoList(lockId: CommonDataManage().currentKeyInfo.lockId!);
|
||||
if (!entity.errorCode!.codeIsSuccessful ||
|
||||
(entity.data?.userNos ?? <int>[]).isEmpty) {
|
||||
final LockUserNoListEntity entity = await ApiRepository.to.getLockUserNoList(lockId: CommonDataManage().currentKeyInfo.lockId!);
|
||||
if (!entity.errorCode!.codeIsSuccessful || (entity.data?.userNos ?? <int>[]).isEmpty) {
|
||||
throw Exception('ApiRepository.to.getLockUserNoList 访问失败');
|
||||
}
|
||||
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
@ -208,7 +206,8 @@ class SenderBeforeDataManage {
|
||||
endTime = DateTime.fromMillisecondsSinceEpoch(currentKeyInfo.endDate!);
|
||||
|
||||
startDateTime = DateTool().dateToTimestamp(DateTool().dateToYMDString(currentKeyInfo.startDate!.toString()), 1) ~/ 1000;
|
||||
endDateTime = (DateTool().dateToTimestamp(DateTool().dateToYMDString(currentKeyInfo.endDate!.toString()), 1) + CommonDataManage().dayLatestTime) ~/ 1000;
|
||||
endDateTime =
|
||||
(DateTool().dateToTimestamp(DateTool().dateToYMDString(currentKeyInfo.endDate!.toString()), 1) + CommonDataManage().dayLatestTime) ~/ 1000;
|
||||
} else if (currentKeyInfo.keyType == XSConstantMacro.keyTypeOnce) {
|
||||
// 单次
|
||||
useCountLimit = 1;
|
||||
@ -217,7 +216,7 @@ class SenderBeforeDataManage {
|
||||
// AppLog.log("startTime.hour:${startTime!.hour} startTime.minute:${startTime!.minute} endTime.hour:${endTime!.hour} endTime.minute:${endTime!.minute}}");
|
||||
final AddUserCommand addUserData = AddUserCommand(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
authUserID: currentKeyInfo.senderUserId!.toString(),
|
||||
authUserID: currentKeyInfo.senderUserId?.toString() ?? '1',
|
||||
keyID: currentKeyInfo.keyId.toString(),
|
||||
userID: await Storage.getUid(),
|
||||
openMode: 1,
|
||||
@ -226,10 +225,7 @@ class SenderBeforeDataManage {
|
||||
expireDate: endDateTime,
|
||||
useCountLimit: useCountLimit,
|
||||
isRound: isRound ? 1 : 0,
|
||||
weekRound: isRound
|
||||
? DateTool().accordingTheCycleIntoTheCorrespondingNumber(
|
||||
currentKeyInfo.weekDays!)
|
||||
: 0,
|
||||
weekRound: isRound ? DateTool().accordingTheCycleIntoTheCorrespondingNumber(currentKeyInfo.weekDays!) : 0,
|
||||
startHour: isRound ? startTime!.hour : 0,
|
||||
startMin: isRound ? startTime!.minute : 0,
|
||||
endHour: isRound ? endTime!.hour : 0,
|
||||
@ -271,8 +267,7 @@ class SenderBeforeDataManage {
|
||||
// 普通用户接收电子钥匙之后 更新锁用户NO
|
||||
Future<void> _updateLockUserNo(List<int> dataList) async {
|
||||
final LockNetTokenEntity entity = await ApiRepository.to.updateLockUserNo(
|
||||
keyId: CommonDataManage().currentKeyInfo.keyId.toString(),
|
||||
lockUserNo: CommonDataManage().currentKeyInfo.lockUserNo.toString());
|
||||
keyId: CommonDataManage().currentKeyInfo.keyId.toString(), lockUserNo: CommonDataManage().currentKeyInfo.lockUserNo.toString());
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
eventBus.fire(RefreshLockListInfoDataEvent());
|
||||
eventBus.fire(LockAddUserSucceedEvent(<int>[0], 0));
|
||||
@ -281,9 +276,8 @@ class SenderBeforeDataManage {
|
||||
|
||||
// 更新锁用户InitUserNo
|
||||
Future<void> _updateLockInitUserNo() async {
|
||||
final LockNetTokenEntity entity = await ApiRepository.to.updateLockInitUserNo(
|
||||
lockId: CommonDataManage().currentKeyInfo.lockId ?? 0,
|
||||
initUserNo: CommonDataManage().currentKeyInfo.initUserNo ?? 0);
|
||||
final LockNetTokenEntity entity = await ApiRepository.to
|
||||
.updateLockInitUserNo(lockId: CommonDataManage().currentKeyInfo.lockId ?? 0, initUserNo: CommonDataManage().currentKeyInfo.initUserNo ?? 0);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
eventBus.fire(RefreshLockListInfoDataEvent());
|
||||
eventBus.fire(LockInitUserNoEvent());
|
||||
|
||||
@ -6,6 +6,8 @@ import 'package:star_lock/blue/io_protocol/io_deletUser.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';
|
||||
import 'package:star_lock/blue/io_protocol/io_readRegisterKey.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_sendAuthorizationCode.dart';
|
||||
|
||||
import 'io_gateway/io_gateway_configuringWifi.dart';
|
||||
import 'io_gateway/io_gateway_getStatus.dart';
|
||||
@ -1109,10 +1111,7 @@ class IoSenderManage {
|
||||
|
||||
//ota 升级过程
|
||||
static void senderProcessOtaUpgradeCommand(
|
||||
{required int? index,
|
||||
required int? size,
|
||||
required List<int>? data,
|
||||
CommandSendCallBack? callBack}) {
|
||||
{required int? index, required int? size, required List<int>? data, CommandSendCallBack? callBack}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: ProcessOtaUpgradeCommand(
|
||||
index: index,
|
||||
@ -1321,8 +1320,7 @@ class IoSenderManage {
|
||||
}
|
||||
|
||||
// 网关获取wifi列表
|
||||
static void gatewayGetWifiCommand(
|
||||
{required String? userID, CommandSendCallBack? callBack}) {
|
||||
static void gatewayGetWifiCommand({required String? userID, CommandSendCallBack? callBack}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: GatewayGetWifiCommand(
|
||||
userID: userID,
|
||||
@ -1339,21 +1337,51 @@ class IoSenderManage {
|
||||
CommandSendCallBack? callBack}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: GatewayConfiguringWifiCommand(
|
||||
ssid: ssid,
|
||||
password: password,
|
||||
gatewayConfigurationStr: gatewayConfigurationStr),
|
||||
ssid: ssid, password: password, gatewayConfigurationStr: gatewayConfigurationStr),
|
||||
isBeforeAddUser: true,
|
||||
callBack: callBack);
|
||||
}
|
||||
|
||||
// 获取网关状态
|
||||
static void gatewayGetStatusCommand(
|
||||
{required String? lockID,
|
||||
required String? userID,
|
||||
CommandSendCallBack? callBack}) {
|
||||
{required String? lockID, required String? userID, CommandSendCallBack? callBack}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: GatewayGetStatusCommand(lockID: lockID, userID: userID),
|
||||
command: GatewayGetStatusCommand(lockID: lockID, userID: userID), isBeforeAddUser: true, callBack: callBack);
|
||||
}
|
||||
|
||||
// 读取注册密钥
|
||||
static void readRegisterKey({
|
||||
required String? lockID,
|
||||
CommandSendCallBack? callBack,
|
||||
}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: SenderReadRegisterKeyCommand(lockID: lockID),
|
||||
isBeforeAddUser: true,
|
||||
callBack: callBack);
|
||||
callBack: callBack,
|
||||
);
|
||||
}
|
||||
|
||||
// 发送授权码
|
||||
static void sendAuthorizationCode({
|
||||
required String? lockID,
|
||||
required String? uuid,
|
||||
required String? key,
|
||||
required String? mac,
|
||||
required int? platform,
|
||||
required int? utcTimeStamp,
|
||||
CommandSendCallBack? callBack,
|
||||
}) {
|
||||
CommandSenderManager().managerSendData(
|
||||
command: SenderAuthorizationCodeCommand(
|
||||
lockID: lockID,
|
||||
uuid: uuid,
|
||||
key: key,
|
||||
mac: mac,
|
||||
platform: platform,
|
||||
utcTimeStamp: utcTimeStamp,
|
||||
),
|
||||
isBeforeAddUser: true,
|
||||
callBack: callBack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ActivateInfoResponse {
|
||||
ActivateInfoResponse({
|
||||
this.description,
|
||||
@ -9,16 +11,99 @@ class ActivateInfoResponse {
|
||||
ActivateInfoResponse.fromJson(dynamic json) {
|
||||
description = json['description'];
|
||||
errorCode = json['errorCode'];
|
||||
// 修改这里:如果 json['data'] 是列表,则解析为 List<ActivateInfo>;否则为空列表
|
||||
data = json['data'] != null
|
||||
? (json['data'] as List).map((item) => ActivateInfo.fromJson(item)).toList()
|
||||
: [];
|
||||
// 修改这里:直接解析为单个 ActivateInfo 对象
|
||||
data = json['data'] != null ? ActivateInfo.fromJson(json['data']) : null;
|
||||
errorMsg = json['errorMsg'];
|
||||
}
|
||||
|
||||
String? description;
|
||||
int? errorCode;
|
||||
List<ActivateInfo>? data; // 改为 List<ActivateInfo>
|
||||
ActivateInfo? data; // 改为 List<ActivateInfo>
|
||||
String? errorMsg;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['description'] = description;
|
||||
map['errorCode'] = errorCode;
|
||||
if (data != null) {
|
||||
// 修改这里:将单个 ActivateInfo 对象转换为 JSON
|
||||
map['data'] = data!.toJson();
|
||||
}
|
||||
map['errorMsg'] = errorMsg;
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActivateInfoResponse{description: $description, errorCode: $errorCode, data: $data, errorMsg: $errorMsg}';
|
||||
}
|
||||
}
|
||||
|
||||
class ActivateInfo {
|
||||
String? authCode;
|
||||
String? activatedAt;
|
||||
Map<String, dynamic>? extraParams; // 修改为 Map 类型
|
||||
|
||||
ActivateInfo({
|
||||
this.authCode,
|
||||
this.activatedAt,
|
||||
this.extraParams,
|
||||
});
|
||||
|
||||
ActivateInfo.fromJson(dynamic json) {
|
||||
authCode = json['auth_code'] ?? '';
|
||||
activatedAt = json['activated_at'] ?? '';
|
||||
// 修改这里:将 extraParams 解析为 Map 对象
|
||||
if (json['extra_params'] != null) {
|
||||
if (json['extra_params'] is Map) {
|
||||
extraParams = json['extra_params'];
|
||||
} else if (json['extra_params'] is String) {
|
||||
// 如果是字符串,尝试解析为 JSON 对象
|
||||
try {
|
||||
extraParams = jsonDecode(json['extra_params']);
|
||||
} catch (e) {
|
||||
// 解析失败则设为 null 或空 map
|
||||
extraParams = {};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
extraParams = {};
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['authCode'] = authCode;
|
||||
map['activatedAt'] = activatedAt;
|
||||
map['extraParams'] = extraParams;
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActivateInfo{authCode: $authCode, activatedAt: $activatedAt, extraParams: $extraParams}';
|
||||
}
|
||||
}
|
||||
|
||||
class TppSupportResponse {
|
||||
TppSupportResponse({
|
||||
this.description,
|
||||
this.errorCode,
|
||||
this.data, // 现在是一个 List<ActivateInfo>
|
||||
this.errorMsg,
|
||||
});
|
||||
|
||||
TppSupportResponse.fromJson(dynamic json) {
|
||||
description = json['description'];
|
||||
errorCode = json['errorCode'];
|
||||
// 修改这里:如果 json['data'] 是列表,则解析为 List<ActivateInfo>;否则为空列表
|
||||
data = json['data'] != null ? (json['data'] as List).map((item) => TppSupportInfo.fromJson(item)).toList() : [];
|
||||
errorMsg = json['errorMsg'];
|
||||
}
|
||||
|
||||
String? description;
|
||||
int? errorCode;
|
||||
List<TppSupportInfo>? data; // 改为 List<ActivateInfo>
|
||||
String? errorMsg;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@ -35,33 +120,28 @@ class ActivateInfoResponse {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActivateInfoResponse{description: $description, errorCode: $errorCode, data: $data, errorMsg: $errorMsg}';
|
||||
return 'TppSupportResponse{description: $description, errorCode: $errorCode, data: $data, errorMsg: $errorMsg}';
|
||||
}
|
||||
}
|
||||
|
||||
class ActivateInfo {
|
||||
String? platformName;
|
||||
class TppSupportInfo {
|
||||
int? platform;
|
||||
String? platformName;
|
||||
|
||||
ActivateInfo({
|
||||
this.platformName,
|
||||
TppSupportInfo({
|
||||
this.platform,
|
||||
this.platformName,
|
||||
});
|
||||
|
||||
ActivateInfo.fromJson(dynamic json) {
|
||||
platformName = json['platformName'] ?? '';
|
||||
platform = json['platform'] ?? '';
|
||||
TppSupportInfo.fromJson(dynamic json) {
|
||||
platform = json['platform'] as int? ?? -1;
|
||||
platformName = json['platform_name'] as String? ?? '';
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['platformName'] = platformName;
|
||||
map['platform'] = platform;
|
||||
map['platform_name'] = platformName;
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActivateInfo{platformName: $platformName, platform: $platform}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,6 +658,7 @@ class LockDetailLogic extends BaseGetXController {
|
||||
if (list.isEmpty) {
|
||||
return;
|
||||
}
|
||||
AppLog.log('list:${list}');
|
||||
final KeyOperationRecordEntity entity =
|
||||
await ApiRepository.to.lockRecordUploadData(lockId: state.keyInfos.value.lockId.toString(), records: list);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
|
||||
@ -1,40 +1,317 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.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_getStarLockStatusInfo.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_readRegisterKey.dart';
|
||||
import 'package:star_lock/blue/io_protocol/io_sendAuthorizationCode.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/blue/sender_manage.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/lockTime/getServerDatetime_entity.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/thirdPartyPlatform/third_party_platform_state.dart';
|
||||
import 'package:star_lock/network/api_repository.dart';
|
||||
import 'package:star_lock/network/start_company_api.dart';
|
||||
import 'package:star_lock/tools/baseGetXController.dart';
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
|
||||
class ThirdPartyPlatformLogic extends BaseGetXController {
|
||||
ThirdPartyPlatformState state = ThirdPartyPlatformState();
|
||||
|
||||
void savePlatFormSetting() {
|
||||
// showEasyLoading();
|
||||
showToast('功能待开放'.tr);
|
||||
// dismissEasyLoading();
|
||||
}
|
||||
|
||||
/// 查询TPP支持
|
||||
void getActivateInfo() async {
|
||||
final model = state.lockSetInfoData.value.lockBasicInfo?.model;
|
||||
if (model != null && model != '') {
|
||||
final response = await StartCompanyApi.to.getActivateInfo(model: model);
|
||||
if (response.errorCode!.codeIsSuccessful) {
|
||||
AppLog.log('${response.data}');
|
||||
}
|
||||
}
|
||||
}
|
||||
// 监听设备返回的数据
|
||||
StreamSubscription<Reply>? _replySubscription;
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
// TODO: implement onReady
|
||||
super.onReady();
|
||||
getActivateInfo();
|
||||
await getActivateInfo();
|
||||
_initReplySubscription();
|
||||
await getServerDatetime();
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
});
|
||||
BlueManage().blueSendData(
|
||||
BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
IoSenderManage.readRegisterKey(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
);
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
dismissEasyLoading();
|
||||
// 安全地取消订阅
|
||||
_replySubscription?.cancel();
|
||||
_replySubscription = null;
|
||||
|
||||
// 添加额外的日志来跟踪清理过程
|
||||
AppLog.log('ThirdPartyPlatformLogic disposed, subscription cancelled');
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
// 安全地取消订阅
|
||||
_replySubscription?.cancel();
|
||||
_replySubscription = null;
|
||||
}
|
||||
|
||||
// 从服务器时间
|
||||
Future<void> getServerDatetime() async {
|
||||
final GetServerDatetimeEntity entity = await ApiRepository.to.getServerDatetimeData(isUnShowLoading: true);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
state.serverTime = entity.data!.date! ~/ 1000;
|
||||
state.differentialTime = entity.data!.date! ~/ 1000 - DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
}
|
||||
}
|
||||
|
||||
int getUTCNetTime() {
|
||||
return DateTime.now().millisecondsSinceEpoch ~/ 1000 + state.differentialTime;
|
||||
}
|
||||
|
||||
void _initReplySubscription() {
|
||||
// 更严格的检查,避免重复初始化
|
||||
if (_replySubscription != null) {
|
||||
AppLog.log('订阅已存在,避免重复初始化');
|
||||
return;
|
||||
}
|
||||
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) async {
|
||||
if (reply is SenderReadRegisterKeyCommandReply) {
|
||||
_handleReadRegisterKeyReply(reply);
|
||||
}
|
||||
|
||||
if (reply is GetStarLockStatuInfoReply) {
|
||||
_replyGetStarLockStatusInfo(reply);
|
||||
}
|
||||
if (reply is SenderAuthorizationCodeCommandReply) {
|
||||
_handleAuthorizationCodeReply(reply);
|
||||
}
|
||||
});
|
||||
AppLog.log('创建新订阅:${_replySubscription.hashCode}');
|
||||
}
|
||||
|
||||
// 获取星锁状态
|
||||
Future<void> _replyGetStarLockStatusInfo(Reply reply) async {
|
||||
final int status = reply.data[2];
|
||||
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
//成功
|
||||
// AppLog.log('获取锁状态成功');
|
||||
// 厂商名称
|
||||
int index = 3;
|
||||
final List<int> vendor = reply.data.sublist(index, index + 20);
|
||||
final String vendorStr = utf8String(vendor);
|
||||
state.lockInfo['vendor'] = vendorStr;
|
||||
// state.lockInfo["vendor"] = "XL";
|
||||
index = index + 20;
|
||||
// AppLog.log('厂商名称 vendorStr:$vendorStr');
|
||||
|
||||
// 锁设备类型
|
||||
final int product = reply.data[index];
|
||||
state.lockInfo['product'] = product;
|
||||
index = index + 1;
|
||||
// AppLog.log('锁设备类型 product:$product');
|
||||
|
||||
// 产品名称
|
||||
final List<int> model = reply.data.sublist(index, index + 20);
|
||||
final String modelStr = utf8String(model);
|
||||
state.lockInfo['model'] = modelStr;
|
||||
// state.lockInfo["model"] = "JL-BLE-01";
|
||||
index = index + 20;
|
||||
// AppLog.log('产品名称 mmodelStr:$modelStr');
|
||||
|
||||
// 软件版本
|
||||
final List<int> fwVersion = reply.data.sublist(index, index + 20);
|
||||
final String fwVersionStr = utf8String(fwVersion);
|
||||
state.lockInfo['fwVersion'] = fwVersionStr;
|
||||
index = index + 20;
|
||||
// AppLog.log('软件版本 fwVersionStr:$fwVersionStr');
|
||||
|
||||
// 硬件版本
|
||||
final List<int> hwVersion = reply.data.sublist(index, index + 20);
|
||||
final String hwVersionStr = utf8String(hwVersion);
|
||||
state.lockInfo['hwVersion'] = hwVersionStr;
|
||||
index = index + 20;
|
||||
// AppLog.log('硬件版本 hwVersionStr:$hwVersionStr');
|
||||
|
||||
// 厂商序列号
|
||||
final List<int> serialNum0 = reply.data.sublist(index, index + 16);
|
||||
final String serialNum0Str = utf8String(serialNum0);
|
||||
state.lockInfo['serialNum0'] = serialNum0Str;
|
||||
// state.lockInfo["serialNum0"] = "${DateTime.now().millisecondsSinceEpoch ~/ 10}";
|
||||
index = index + 16;
|
||||
// AppLog.log('厂商序列号 serialNum0Str:$serialNum0Str');
|
||||
final response = await StartCompanyApi.to.getAuthorizationCode(
|
||||
registerKey: state.registerKey.value,
|
||||
model: modelStr,
|
||||
serialNum0: serialNum0Str,
|
||||
platform: 1,
|
||||
);
|
||||
if (response.errorCode!.codeIsSuccessful) {
|
||||
BlueManage().blueSendData(
|
||||
BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
IoSenderManage.sendAuthorizationCode(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
uuid: response.data?.extraParams?['uuid'],
|
||||
key: response.data?.authCode,
|
||||
mac: response.data?.extraParams?['mac'],
|
||||
platform: state.selectPlatFormIndex.value,
|
||||
utcTimeStamp: getUTCNetTime(),
|
||||
);
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
}
|
||||
},
|
||||
isAddEquipment: true,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 0x06:
|
||||
//无权限
|
||||
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
|
||||
IoSenderManage.senderGetStarLockStatuInfo(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
userID: await Storage.getUid(),
|
||||
utcTimeStamp: 0,
|
||||
unixTimeStamp: 0,
|
||||
isBeforeAddUser: false,
|
||||
privateKey: getPrivateKeyList,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void savePlatFormSetting() {
|
||||
if (state.selectPlatFormIndex.value == 1 || state.selectPlatFormIndex.value == 0) {
|
||||
if (state.registerKey.isNotEmpty) {
|
||||
_requestAuthorizationCode();
|
||||
}
|
||||
} else {
|
||||
showToast('目前只支持切换至涂鸦智能、锁通通协议'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
/// 查询TPP支持
|
||||
Future<void> getActivateInfo() async {
|
||||
final model = state.lockSetInfoData.value.lockBasicInfo?.model;
|
||||
if (model != null && model != '') {
|
||||
final response = await StartCompanyApi.to.getTppSupport(model: model);
|
||||
if (response.errorCode!.codeIsSuccessful) {
|
||||
response.data?.forEach((element) {
|
||||
state.tppSupportList.add(element);
|
||||
});
|
||||
state.tppSupportList.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleReadRegisterKeyReply(SenderReadRegisterKeyCommandReply reply) {
|
||||
final int status = reply.data[6];
|
||||
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
final platform = reply.data[7];
|
||||
// 提取 RegisterKey (从第7个字节开始,长度为40)
|
||||
final List<int> registerKeyBytes = reply.data.sublist(8, 48);
|
||||
final String registerKey = String.fromCharCodes(registerKeyBytes);
|
||||
state.selectPlatFormIndex.value = platform;
|
||||
print('platform: $platform');
|
||||
print('Register Key: $registerKey');
|
||||
state.registerKey.value = registerKey;
|
||||
//成功
|
||||
cancelBlueConnetctToastTimer();
|
||||
dismissEasyLoading();
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _requestAuthorizationCode() async {
|
||||
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!);
|
||||
IoSenderManage.senderGetStarLockStatuInfo(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
userID: await Storage.getUid(),
|
||||
utcTimeStamp: state.serverTime,
|
||||
unixTimeStamp: getLocalTime2(),
|
||||
isBeforeAddUser: false,
|
||||
privateKey: getPrivateKeyList,
|
||||
);
|
||||
} else if (deviceConnectionState == BluetoothConnectionState.disconnected) {
|
||||
//失败
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int getLocalTime() {
|
||||
final DateTime now = DateTime.now();
|
||||
final Duration timeZoneOffset = now.timeZoneOffset;
|
||||
return state.differentialTime + timeZoneOffset.inSeconds;
|
||||
}
|
||||
|
||||
int getLocalTime2() {
|
||||
final DateTime now = DateTime.now();
|
||||
final Duration timeZoneOffset = now.timeZoneOffset;
|
||||
return state.serverTime + timeZoneOffset.inSeconds;
|
||||
}
|
||||
|
||||
void _handleAuthorizationCodeReply(SenderAuthorizationCodeCommandReply reply) {
|
||||
final int status = reply.data[6];
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
cancelBlueConnetctToastTimer();
|
||||
dismissEasyLoading();
|
||||
if (state.selectPlatFormIndex.value == 1) {
|
||||
showSuccess('操作成功,请尽快用"涂鸦”APP配置,如不使用请关闭该设置支持'.tr);
|
||||
} else if (state.selectPlatFormIndex.value == 0) {
|
||||
showSuccess('操作成功'.tr);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,42 +45,56 @@ class _ThirdPartyPlatformPageState extends State<ThirdPartyPlatformPage> {
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return Stack(
|
||||
return SingleChildScrollView(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height - 100.h,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// 1. 背景或空白区域(可选)
|
||||
Container(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.all(16.w),
|
||||
),
|
||||
Positioned(
|
||||
top: 30.h,
|
||||
left: 50.w,
|
||||
child: Image.asset(
|
||||
'images/other/tuya.png',
|
||||
height: 80.h,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 130.h,
|
||||
left: 150.w,
|
||||
right: 150.w,
|
||||
child: Image.asset(
|
||||
'images/other/2.png',
|
||||
height: 220.h,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
// 2. 顶部图标行:使用 Row 并排显示
|
||||
Positioned(
|
||||
top: 80.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 均匀分布
|
||||
top: 400.h,
|
||||
right: 50.w,
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'images/other/tuya.png',
|
||||
height: 50.h,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Image.asset(
|
||||
'images/other/2.png', // 中间图标
|
||||
height: 80.h,
|
||||
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Image.asset(
|
||||
'images/other/matter.png',
|
||||
width: 110.w,
|
||||
fit: BoxFit.cover,
|
||||
width: 280.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 220.h,
|
||||
top: 530.h,
|
||||
left: 20.w,
|
||||
right: 20.w,
|
||||
child: Text(
|
||||
@ -97,7 +111,7 @@ class _ThirdPartyPlatformPageState extends State<ThirdPartyPlatformPage> {
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 320.h,
|
||||
top: 620.h,
|
||||
bottom: 0,
|
||||
child: ListView.builder(
|
||||
itemCount: state.platFormSet.length,
|
||||
@ -114,7 +128,8 @@ class _ThirdPartyPlatformPageState extends State<ThirdPartyPlatformPage> {
|
||||
// 最后一个元素不显示分割线(取反)
|
||||
isHaveDirection: false,
|
||||
isHaveRightWidget: true,
|
||||
rightWidget: Radio<String>(
|
||||
rightWidget: Obx(
|
||||
() => Radio<String>(
|
||||
// Radio 的值:使用平台的唯一标识(如 id)
|
||||
value: platform,
|
||||
// 当前选中的值:与 selectPlatFormIndex 关联的 id
|
||||
@ -134,6 +149,7 @@ class _ThirdPartyPlatformPageState extends State<ThirdPartyPlatformPage> {
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
action: () {
|
||||
setState(() {
|
||||
state.selectPlatFormIndex.value = index;
|
||||
@ -146,6 +162,8 @@ class _ThirdPartyPlatformPageState extends State<ThirdPartyPlatformPage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/get_rx.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockDetail/ActivateInfoResponse.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
|
||||
import 'package:star_lock/translations/app_dept.dart';
|
||||
|
||||
@ -11,6 +13,7 @@ class ThirdPartyPlatformState {
|
||||
}
|
||||
|
||||
Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
|
||||
int differentialTime = 0; // 服务器时间即UTC+0时间
|
||||
|
||||
// 响应式字符串集合(自动触发 UI 更新)
|
||||
final RxList<String> platFormSet = List.of({
|
||||
@ -19,7 +22,14 @@ class ThirdPartyPlatformState {
|
||||
'Matter'.tr,
|
||||
}).obs;
|
||||
|
||||
// 响应式字符串集合(自动触发 UI 更新)
|
||||
final RxList<TppSupportInfo> tppSupportList = RxList<TppSupportInfo>([]);
|
||||
|
||||
RxInt selectPlatFormIndex = 0.obs;
|
||||
RxBool openNumber = false.obs;
|
||||
RxString registerKey = ''.obs;
|
||||
|
||||
Map lockInfo = {};
|
||||
|
||||
int serverTime = 0; // 服务器时间即UTC+0时间
|
||||
}
|
||||
|
||||
@ -32,12 +32,10 @@ class LockListLogic extends BaseGetXController {
|
||||
final ShowTipView showTipView = ShowTipView();
|
||||
|
||||
List<GroupList> get groupDataListFiltered {
|
||||
final List<GroupList> list =
|
||||
groupDataList.map((GroupList e) => e.copy()).toList();
|
||||
final List<GroupList> list = groupDataList.map((GroupList e) => e.copy()).toList();
|
||||
if (state.searchStr.value != '' && state.showSearch.value) {
|
||||
list.forEach((GroupList element) {
|
||||
element.lockList?.removeWhere((LockListInfoItemEntity element) =>
|
||||
!(element.lockAlias?.contains(state.searchStr.value) ?? true));
|
||||
element.lockList?.removeWhere((LockListInfoItemEntity element) => !(element.lockAlias?.contains(state.searchStr.value) ?? true));
|
||||
});
|
||||
}
|
||||
if (list.length > 0) {
|
||||
@ -46,8 +44,7 @@ class LockListLogic extends BaseGetXController {
|
||||
final lockList = element.lockList;
|
||||
if (lockList != null && lockList.length > 0) {
|
||||
lockList.forEach((element) {
|
||||
if (element.network?.peerId != null &&
|
||||
element.network?.peerId != '') {
|
||||
if (element.network?.peerId != null && element.network?.peerId != '') {
|
||||
StartChartManage().lockListPeerId.add(element);
|
||||
}
|
||||
});
|
||||
@ -68,11 +65,9 @@ class LockListLogic extends BaseGetXController {
|
||||
late StreamSubscription _setLockListInfoGroupEntity;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) async {
|
||||
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) async {
|
||||
// 恢复出厂设置
|
||||
if ((reply is FactoryDataResetReply) &&
|
||||
(state.ifCurrentScreen.value == true)) {
|
||||
if ((reply is FactoryDataResetReply)) {
|
||||
_replyFactoryDataResetKey(reply);
|
||||
}
|
||||
});
|
||||
@ -93,19 +88,18 @@ class LockListLogic extends BaseGetXController {
|
||||
break;
|
||||
case 0x06:
|
||||
//无权限
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
final List<int> getTokenList = changeStringListToIntList(token!);
|
||||
|
||||
final List<int> tokenData = reply.data.sublist(2, 6);
|
||||
final List<String> saveStrList = changeIntListToStringList(tokenData);
|
||||
Storage.setStringList(saveBlueToken, saveStrList);
|
||||
IoSenderManage.senderFactoryDataReset(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
userID: await Storage.getUid(),
|
||||
keyID: '1',
|
||||
needAuthor: 1,
|
||||
publicKey:
|
||||
state.lockListInfoItemEntity.bluetooth!.publicKey!.cast<int>(),
|
||||
privateKey:
|
||||
state.lockListInfoItemEntity.bluetooth!.privateKey!.cast<int>(),
|
||||
token: getTokenList);
|
||||
publicKey: state.publicKey,
|
||||
privateKey: state.privateKey,
|
||||
token: tokenData,
|
||||
);
|
||||
break;
|
||||
case 0x07:
|
||||
//无权限
|
||||
@ -146,8 +140,7 @@ class LockListLogic extends BaseGetXController {
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLong ||
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLoop) {
|
||||
// 当是正常使用跟待接收状态的时候
|
||||
if (keyInfo.keyStatus == XSConstantMacro.keyStatusNormalUse ||
|
||||
keyInfo.keyStatus == XSConstantMacro.keyStatusWaitReceive) {
|
||||
if (keyInfo.keyStatus == XSConstantMacro.keyStatusNormalUse || keyInfo.keyStatus == XSConstantMacro.keyStatusWaitReceive) {
|
||||
return "${"余".tr}${DateTool().compareTimeGetDaysFromNow(keyInfo.endDate!)}${"天".tr}";
|
||||
} else {
|
||||
return XSConstantMacro.getKeyStatusStr(keyInfo.keyStatus!);
|
||||
@ -160,11 +153,7 @@ class LockListLogic extends BaseGetXController {
|
||||
|
||||
//判断是否要显示提示
|
||||
bool getShowType(LockListInfoItemEntity keyInfo) {
|
||||
final List<int> keyTypes = <int>[
|
||||
XSConstantMacro.keyTypeTime,
|
||||
XSConstantMacro.keyTypeOnce,
|
||||
XSConstantMacro.keyTypeLoop
|
||||
];
|
||||
final List<int> keyTypes = <int>[XSConstantMacro.keyTypeTime, XSConstantMacro.keyTypeOnce, XSConstantMacro.keyTypeLoop];
|
||||
final List<int> keyStatus = <int>[
|
||||
XSConstantMacro.keyStatusWaitIneffective,
|
||||
XSConstantMacro.keyStatusFrozen,
|
||||
@ -172,36 +161,30 @@ class LockListLogic extends BaseGetXController {
|
||||
];
|
||||
|
||||
//新增以上组合未包含--永久&&冻结状态 显示
|
||||
final bool isLongFrozenStatus =
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLong &&
|
||||
keyInfo.keyStatus == XSConstantMacro.keyStatusFrozen;
|
||||
final DateTime endDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(keyInfo.endDate ?? 0);
|
||||
final bool isLongFrozenStatus = keyInfo.keyType == XSConstantMacro.keyTypeLong && keyInfo.keyStatus == XSConstantMacro.keyStatusFrozen;
|
||||
final DateTime endDate = DateTime.fromMillisecondsSinceEpoch(keyInfo.endDate ?? 0);
|
||||
final DateTime now = DateTime.now();
|
||||
final bool isKeyType = keyTypes.contains(keyInfo.keyType);
|
||||
final bool isKeyStatus = keyStatus.contains(keyInfo.keyStatus);
|
||||
final Duration difference = endDate.difference(now);
|
||||
final bool isExpirationSoon = isKeyType && difference.inDays <= 15;
|
||||
final bool isShow =
|
||||
isKeyType && isKeyStatus || isExpirationSoon || isLongFrozenStatus;
|
||||
final bool isShow = isKeyType && isKeyStatus || isExpirationSoon || isLongFrozenStatus;
|
||||
return isShow;
|
||||
}
|
||||
|
||||
/// 以下为删除逻辑
|
||||
void deleyLockLogicOfRoles() {
|
||||
if (state.lockListInfoItemEntity.isLockOwner == 1) {
|
||||
void deleyLockLogicOfRoles(LockListInfoItemEntity keyInfo) {
|
||||
if (state.lockListInfoItemEntity.value.isLockOwner == 1) {
|
||||
// 超级管理员必须通过连接蓝牙删除
|
||||
showTipView.showIosTipWithContentDialog('删除锁后,所有信息都会一起删除,确定删除锁吗?'.tr, () {
|
||||
// 删除锁
|
||||
AppLog.log('调用了删除锁');
|
||||
showTipView.resetGetController();
|
||||
showTipView.showTFViewAlertDialog(
|
||||
state.passwordTF, '请输入登录密码'.tr, '请输入登录密码'.tr, checkLoginPassword);
|
||||
showTipView.showTFViewAlertDialog(state.passwordTF, '请输入登录密码'.tr, '请输入登录密码'.tr, () => checkLoginPassword(keyInfo));
|
||||
});
|
||||
} else if (state.lockListInfoItemEntity.keyRight == 1) {
|
||||
} else if (state.lockListInfoItemEntity.value.keyRight == 1) {
|
||||
// 授权管理员弹框提示
|
||||
showTipView.showDeleteAdministratorIsHaveAllDataDialog(
|
||||
'同时删除其发送的所有钥匙,钥匙删除后不能恢复'.tr, (bool a) {
|
||||
showTipView.showDeleteAdministratorIsHaveAllDataDialog('同时删除其发送的所有钥匙,钥匙删除后不能恢复'.tr, (bool a) {
|
||||
// 授权管理员删除
|
||||
state.deleteAdministratorIsHaveAllData.value = a;
|
||||
deletKeyData();
|
||||
@ -213,20 +196,20 @@ class LockListLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
// 查询账户密码
|
||||
Future<void> checkLoginPassword() async {
|
||||
Future<void> checkLoginPassword(LockListInfoItemEntity keyInfo) async {
|
||||
final LockListInfoEntity entity = await ApiRepository.to.checkLoginPassword(
|
||||
password: state.passwordTF.text,
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
Get.back();
|
||||
factoryDataResetAction();
|
||||
factoryDataResetAction(keyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 当是锁拥有者的时候,删除锁
|
||||
Future<void> deletLockInfoData() async {
|
||||
final LockListInfoEntity entity = await ApiRepository.to.deletOwnerLockData(
|
||||
lockId: state.lockListInfoItemEntity.lockId!,
|
||||
lockId: state.lockListInfoItemEntity.value.lockId!,
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
BlueManage().connectDeviceMacAddress = '';
|
||||
@ -239,10 +222,9 @@ class LockListLogic extends BaseGetXController {
|
||||
// 普通用户或者授权管理员删除钥匙
|
||||
Future<void> deletKeyData() async {
|
||||
final LockListInfoEntity entity = await ApiRepository.to.deletOwnerKeyData(
|
||||
lockId: state.lockListInfoItemEntity.lockId.toString(),
|
||||
keyId: state.lockListInfoItemEntity.keyId.toString(),
|
||||
includeUnderlings:
|
||||
state.deleteAdministratorIsHaveAllData.value == true ? 1 : 0);
|
||||
lockId: state.lockListInfoItemEntity.value.lockId.toString(),
|
||||
keyId: state.lockListInfoItemEntity.value.keyId.toString(),
|
||||
includeUnderlings: state.deleteAdministratorIsHaveAllData.value == true ? 1 : 0);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
BlueManage().connectDeviceMacAddress = '';
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
@ -252,7 +234,7 @@ class LockListLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
// 恢复出厂设置
|
||||
Future<void> factoryDataResetAction() async {
|
||||
Future<void> factoryDataResetAction(LockListInfoItemEntity keyInfo) async {
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(
|
||||
isShowBlueConnetctToast: false,
|
||||
@ -260,31 +242,23 @@ class LockListLogic extends BaseGetXController {
|
||||
dismissEasyLoading();
|
||||
showDeletAlertTipDialog();
|
||||
});
|
||||
BlueManage().blueSendData(state.lockListInfoItemEntity.lockName!,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
BlueManage().blueSendData(state.lockListInfoItemEntity.value.lockName!, (BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
final List<int> publicKeyData =
|
||||
state.lockListInfoItemEntity.bluetooth!.publicKey!.cast<int>();
|
||||
final List<String> saveStrList =
|
||||
changeIntListToStringList(publicKeyData);
|
||||
final List<int> publicKeyData = state.lockListInfoItemEntity.value.bluetooth!.publicKey!.cast<int>();
|
||||
final List<String> saveStrList = changeIntListToStringList(publicKeyData);
|
||||
await Storage.setStringList(saveBluePublicKey, saveStrList);
|
||||
|
||||
// 私钥
|
||||
final List<int> privateKeyData =
|
||||
state.lockListInfoItemEntity.bluetooth!.privateKey!.cast<int>();
|
||||
final List<String> savePrivateKeyList =
|
||||
changeIntListToStringList(privateKeyData);
|
||||
final List<int> privateKeyData = state.lockListInfoItemEntity.value.bluetooth!.privateKey!.cast<int>();
|
||||
final List<String> savePrivateKeyList = changeIntListToStringList(privateKeyData);
|
||||
await Storage.setStringList(saveBluePrivateKey, savePrivateKeyList);
|
||||
|
||||
// signKey
|
||||
final List<int> signKeyData =
|
||||
state.lockListInfoItemEntity.bluetooth!.signKey!.cast<int>();
|
||||
final List<String> saveSignKeyList =
|
||||
changeIntListToStringList(signKeyData);
|
||||
final List<int> signKeyData = state.lockListInfoItemEntity.value.bluetooth!.signKey!.cast<int>();
|
||||
final List<String> saveSignKeyList = changeIntListToStringList(signKeyData);
|
||||
await Storage.setStringList(saveBlueSignKey, saveSignKeyList);
|
||||
|
||||
final List<String> saveTokenList =
|
||||
changeIntListToStringList(<int>[0, 0, 0, 0]);
|
||||
final List<String> saveTokenList = changeIntListToStringList(<int>[0, 0, 0, 0]);
|
||||
await Storage.setStringList(saveBlueToken, saveTokenList);
|
||||
|
||||
IoSenderManage.senderFactoryDataReset(
|
||||
@ -292,11 +266,13 @@ class LockListLogic extends BaseGetXController {
|
||||
userID: await Storage.getUid(),
|
||||
keyID: '1',
|
||||
needAuthor: 1,
|
||||
publicKey:
|
||||
state.lockListInfoItemEntity.bluetooth!.publicKey!.cast<int>(),
|
||||
privateKey:
|
||||
state.lockListInfoItemEntity.bluetooth!.privateKey!.cast<int>(),
|
||||
publicKey: keyInfo.bluetooth?.publicKey ?? [],
|
||||
privateKey: keyInfo.bluetooth?.privateKey ?? [],
|
||||
token: <int>[0, 0, 0, 0]);
|
||||
state.publicKey.value = keyInfo.bluetooth?.publicKey ?? [];
|
||||
state.privateKey.value = keyInfo.bluetooth?.privateKey ?? [];
|
||||
state.publicKey.refresh();
|
||||
state.privateKey.refresh();
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
@ -353,9 +329,7 @@ class LockListLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
void _initEventHandler() {
|
||||
_setLockListInfoGroupEntity = eventBus
|
||||
.on<SetLockListInfoGroupEntity>()
|
||||
.listen((SetLockListInfoGroupEntity event) async {
|
||||
_setLockListInfoGroupEntity = eventBus.on<SetLockListInfoGroupEntity>().listen((SetLockListInfoGroupEntity event) async {
|
||||
setLockListInfoGroupEntity(event.lockListInfoGroupEntity);
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import 'package:star_lock/main/lockMian/lockList/lockList_state.dart';
|
||||
|
||||
import '../../../appRouters.dart';
|
||||
@ -14,8 +15,7 @@ import 'lockListGroup_view.dart';
|
||||
import 'lockList_logic.dart';
|
||||
|
||||
class LockListPage extends StatefulWidget {
|
||||
const LockListPage({required this.lockListInfoGroupEntity, Key? key})
|
||||
: super(key: key);
|
||||
const LockListPage({required this.lockListInfoGroupEntity, Key? key}) : super(key: key);
|
||||
final LockListInfoGroupEntity lockListInfoGroupEntity;
|
||||
|
||||
@override
|
||||
@ -30,16 +30,15 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
void initState() {
|
||||
super.initState();
|
||||
logic = Get.put(LockListLogic(widget.lockListInfoGroupEntity));
|
||||
state = Get
|
||||
.find<LockListLogic>()
|
||||
.state;
|
||||
state = Get.find<LockListLogic>().state;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() => Scaffold(
|
||||
return Obx(
|
||||
() => Scaffold(
|
||||
body: ListView.separated(
|
||||
itemCount: logic.groupDataListFiltered.length,
|
||||
itemCount: logic.groupDataList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final GroupList itemData = logic.groupDataListFiltered[index];
|
||||
return _buildLockExpandedList(context, index, itemData, key: ValueKey(itemData.groupId));
|
||||
@ -51,20 +50,21 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
height: 1,
|
||||
color: AppColors.greyLineColor,
|
||||
);
|
||||
}),
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//设备多层级列表
|
||||
Widget _buildLockExpandedList(BuildContext context, int index,
|
||||
GroupList itemData, {Key? key}) {
|
||||
final List<LockListInfoItemEntity> lockItemList =
|
||||
itemData.lockList ?? <LockListInfoItemEntity>[];
|
||||
Widget _buildLockExpandedList(BuildContext context, int index, GroupList itemData, {Key? key}) {
|
||||
final List<LockListInfoItemEntity> lockItemList = itemData.lockList ?? <LockListInfoItemEntity>[];
|
||||
return LockListGroupView(
|
||||
key: key,
|
||||
onTap: () {
|
||||
//是否选中组
|
||||
if (itemData.isChecked) {} else {}
|
||||
if (itemData.isChecked) {
|
||||
} else {}
|
||||
setState(() {});
|
||||
},
|
||||
typeImgList: const <dynamic>[],
|
||||
@ -91,8 +91,12 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
children: <Widget>[
|
||||
SlidableAction(
|
||||
onPressed: (BuildContext context) {
|
||||
state.lockListInfoItemEntity = keyInfo;
|
||||
logic.deleyLockLogicOfRoles();
|
||||
state.publicKey.value = keyInfo.bluetooth?.publicKey ?? [];
|
||||
state.privateKey.value = keyInfo.bluetooth?.privateKey ?? [];
|
||||
state.lockListInfoItemEntity.value = keyInfo;
|
||||
state.lockListInfoItemEntity.refresh();
|
||||
AppLog.log('msg=================:${state.lockListInfoItemEntity.value}');
|
||||
logic.deleyLockLogicOfRoles(keyInfo);
|
||||
},
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
@ -102,10 +106,8 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
],
|
||||
),
|
||||
child: lockInfoListItem(keyInfo, isLast, () {
|
||||
if ((keyInfo.keyType == XSConstantMacro.keyTypeTime ||
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
(keyInfo.keyStatus ==
|
||||
XSConstantMacro.keyStatusWaitIneffective)) {
|
||||
if ((keyInfo.keyType == XSConstantMacro.keyTypeTime || keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
(keyInfo.keyStatus == XSConstantMacro.keyStatusWaitIneffective)) {
|
||||
logic.showToast('您的钥匙未生效'.tr);
|
||||
return;
|
||||
}
|
||||
@ -116,14 +118,12 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
logic.showToast('您的钥匙已冻结'.tr);
|
||||
return;
|
||||
}
|
||||
if ((keyInfo.keyType == XSConstantMacro.keyTypeTime ||
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
if ((keyInfo.keyType == XSConstantMacro.keyTypeTime || keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
(keyInfo.keyStatus == XSConstantMacro.keyStatusExpired)) {
|
||||
logic.showToast('您的钥匙已过期'.tr);
|
||||
return;
|
||||
}
|
||||
Get.toNamed(Routers.lockDetailMainPage,
|
||||
arguments: <String, Object>{
|
||||
Get.toNamed(Routers.lockDetailMainPage, arguments: <String, Object>{
|
||||
// "lockMainEntity": widget.lockMainEntity,
|
||||
'keyInfo': keyInfo,
|
||||
'isOnlyOneData': false,
|
||||
@ -134,26 +134,18 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
);
|
||||
}
|
||||
|
||||
Widget lockInfoListItem(LockListInfoItemEntity keyInfo, bool isLast,
|
||||
Function() action) {
|
||||
Widget lockInfoListItem(LockListInfoItemEntity keyInfo, bool isLast, Function() action) {
|
||||
return GestureDetector(
|
||||
onTap: action,
|
||||
child: Container(
|
||||
// height: 122.h,
|
||||
margin: isLast
|
||||
? EdgeInsets.only(left: 20.w, right: 20.w, top: 20.w, bottom: 20.w)
|
||||
: EdgeInsets.only(left: 20.w, right: 20.w, top: 20.w),
|
||||
margin: isLast ? EdgeInsets.only(left: 20.w, right: 20.w, top: 20.w, bottom: 20.w) : EdgeInsets.only(left: 20.w, right: 20.w, top: 20.w),
|
||||
decoration: BoxDecoration(
|
||||
color: (((keyInfo.keyType == XSConstantMacro.keyTypeTime ||
|
||||
keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
(keyInfo.keyStatus ==
|
||||
XSConstantMacro.keyStatusWaitIneffective ||
|
||||
keyInfo.keyStatus ==
|
||||
XSConstantMacro.keyStatusFrozen ||
|
||||
keyInfo.keyStatus ==
|
||||
XSConstantMacro.keyStatusExpired)) ||
|
||||
(keyInfo.keyType == XSConstantMacro.keyTypeLong &&
|
||||
keyInfo.keyStatus == XSConstantMacro.keyStatusFrozen))
|
||||
color: (((keyInfo.keyType == XSConstantMacro.keyTypeTime || keyInfo.keyType == XSConstantMacro.keyTypeLoop) &&
|
||||
(keyInfo.keyStatus == XSConstantMacro.keyStatusWaitIneffective ||
|
||||
keyInfo.keyStatus == XSConstantMacro.keyStatusFrozen ||
|
||||
keyInfo.keyStatus == XSConstantMacro.keyStatusExpired)) ||
|
||||
(keyInfo.keyType == XSConstantMacro.keyTypeLong && keyInfo.keyStatus == XSConstantMacro.keyStatusFrozen))
|
||||
? AppColors.greyBackgroundColor
|
||||
: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20.w),
|
||||
@ -177,9 +169,7 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
style: TextStyle(
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: keyInfo.passageMode == 1
|
||||
? AppColors.openPassageModeColor
|
||||
: AppColors.darkGrayTextColor),
|
||||
color: keyInfo.passageMode == 1 ? AppColors.openPassageModeColor : AppColors.darkGrayTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -193,8 +183,7 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
SizedBox(width: 2.w),
|
||||
Text(
|
||||
'${keyInfo.electricQuantity!}%',
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
style: TextStyle(fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
),
|
||||
SizedBox(width: 30.w),
|
||||
],
|
||||
@ -211,10 +200,7 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
borderRadius: BorderRadius.circular(5.w),
|
||||
color: AppColors.openPassageModeColor,
|
||||
),
|
||||
child: Text('常开模式开启'.tr,
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
color: AppColors.appBarIconColor)),
|
||||
child: Text('常开模式开启'.tr, style: TextStyle(fontSize: 18.sp, color: AppColors.appBarIconColor)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
@ -226,8 +212,7 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
SizedBox(width: 30.w),
|
||||
Text(
|
||||
'远程开锁'.tr,
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
style: TextStyle(fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
),
|
||||
],
|
||||
)),
|
||||
@ -241,13 +226,9 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
padding: EdgeInsets.only(right: 5.w, left: 5.w),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.w),
|
||||
color:
|
||||
DateTool().compareTimeIsOvertime(keyInfo.endDate!)
|
||||
? AppColors.listTimeYellowColor
|
||||
: AppColors.mainColor,
|
||||
color: DateTool().compareTimeIsOvertime(keyInfo.endDate!) ? AppColors.listTimeYellowColor : AppColors.mainColor,
|
||||
),
|
||||
child: Text(logic.getKeyEffective(keyInfo),
|
||||
style: TextStyle(fontSize: 18.sp, color: Colors.white)
|
||||
child: Text(logic.getKeyEffective(keyInfo), style: TextStyle(fontSize: 18.sp, color: Colors.white)
|
||||
// child: Text(logic.compareTimeIsOvertime(keyInfo.endDate!) ? "已过期" : "余${logic.compareTimeGetDaysFromNow(keyInfo.endDate!)}天", style: TextStyle(fontSize: 18.sp, color: Colors.white)
|
||||
),
|
||||
),
|
||||
@ -258,13 +239,8 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
children: <Widget>[
|
||||
SizedBox(width: 30.w),
|
||||
Text(
|
||||
"${logic.getUseKeyTypeStr(keyInfo.startDate, keyInfo.endDate,
|
||||
keyInfo.keyType)}/${keyInfo.isLockOwner == 1
|
||||
? '超级管理员'.tr
|
||||
: (keyInfo.keyRight == 1 ? "授权管理员".tr : "普通用户"
|
||||
.tr)}",
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
"${logic.getUseKeyTypeStr(keyInfo.startDate, keyInfo.endDate, keyInfo.keyType)}/${keyInfo.isLockOwner == 1 ? '超级管理员'.tr : (keyInfo.keyRight == 1 ? "授权管理员".tr : "普通用户".tr)}",
|
||||
style: TextStyle(fontSize: 18.sp, color: AppColors.darkGrayTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -286,13 +262,10 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
|
||||
@override
|
||||
void dispose() {
|
||||
Get.delete<LockListLogic>();
|
||||
|
||||
/// 取消路由订阅
|
||||
AppRouteObserver().routeObserver.unsubscribe(this);
|
||||
super
|
||||
.
|
||||
dispose
|
||||
(
|
||||
);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 从上级界面进入 当前界面即将出现
|
||||
|
||||
@ -7,11 +7,14 @@ import '../entity/lockListInfo_entity.dart';
|
||||
class LockListState{
|
||||
|
||||
RxBool itemStatusIsEable = false.obs; // 列表里面item是否能点击
|
||||
LockListInfoItemEntity lockListInfoItemEntity = LockListInfoItemEntity(); // 当前选中要删除的item
|
||||
// 修改后
|
||||
Rx<LockListInfoItemEntity> lockListInfoItemEntity = LockListInfoItemEntity().obs; // 当前选中要删除的item
|
||||
TextEditingController passwordTF = TextEditingController();
|
||||
RxBool deleteAdministratorIsHaveAllData = false.obs; // 删除管理员是否有所有数据
|
||||
RxBool ifCurrentScreen = true.obs; // 是否是当前界面,用于判断是否需要针对当前界面进行展示
|
||||
RxBool showSearch = false.obs; // 是否是当前界面,用于判断是否需要针对当前界面进行展示
|
||||
RxString searchStr = ''.obs; // 是否是当前界面,用于判断是否需要针对当前界面进行展示
|
||||
RxList<int> publicKey=<int>[].obs;
|
||||
RxList<int> privateKey=<int>[].obs;
|
||||
|
||||
}
|
||||
@ -192,8 +192,9 @@ class _LockListXHJPageState extends State<LockListXHJPage> with RouteAware {
|
||||
children: <Widget>[
|
||||
SlidableAction(
|
||||
onPressed: (BuildContext context) {
|
||||
state.lockListInfoItemEntity = keyInfo;
|
||||
logic.deleyLockLogicOfRoles();
|
||||
state.lockListInfoItemEntity.value = keyInfo;
|
||||
state.lockListInfoItemEntity.refresh();
|
||||
logic.deleyLockLogicOfRoles(keyInfo);
|
||||
},
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
|
||||
@ -31,8 +31,7 @@ class SaveLockLogic extends BaseGetXController {
|
||||
late StreamSubscription<Reply> _replySubscription;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
if (reply is AddUserReply && state.ifCurrentScreen.value == true) {
|
||||
_replyAddUserKey(reply);
|
||||
}
|
||||
@ -66,15 +65,11 @@ class SaveLockLogic extends BaseGetXController {
|
||||
break;
|
||||
case 0x06:
|
||||
//无权限
|
||||
final List<String>? privateKey =
|
||||
await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList =
|
||||
changeStringListToIntList(privateKey!);
|
||||
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
|
||||
|
||||
final List<String>? publicKey =
|
||||
await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> publicKeyDataList =
|
||||
changeStringListToIntList(publicKey!);
|
||||
final List<String>? publicKey = await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> publicKeyDataList = changeStringListToIntList(publicKey!);
|
||||
|
||||
IoSenderManage.senderAddUser(
|
||||
lockID: BlueManage().connectDeviceName,
|
||||
@ -215,19 +210,14 @@ class SaveLockLogic extends BaseGetXController {
|
||||
showBlueConnetctToast();
|
||||
}
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState deviceConnectionState) async {
|
||||
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>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
|
||||
|
||||
final List<String>? publicKey =
|
||||
await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> publicKeyDataList =
|
||||
changeStringListToIntList(publicKey!);
|
||||
final List<String>? publicKey = await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> publicKeyDataList = changeStringListToIntList(publicKey!);
|
||||
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
List<int> getTokenList = <int>[0, 0, 0, 0];
|
||||
@ -257,8 +247,7 @@ class SaveLockLogic extends BaseGetXController {
|
||||
privateKey: getPrivateKeyList,
|
||||
token: getTokenList,
|
||||
isBeforeAddUser: true);
|
||||
} else if (deviceConnectionState ==
|
||||
BluetoothConnectionState.disconnected) {
|
||||
} else if (deviceConnectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
state.sureBtnState.value = 0;
|
||||
@ -376,16 +365,14 @@ class SaveLockLogic extends BaseGetXController {
|
||||
// positionMap['address'] = state.addressInfo['address'];
|
||||
|
||||
final Map<String, dynamic> bluetooth = <String, dynamic>{};
|
||||
bluetooth['bluetoothDeviceId'] = BlueManage().connectDeviceMacAddress;
|
||||
bluetooth['bluetoothDeviceId'] = state.lockInfo['mac'];
|
||||
bluetooth['bluetoothDeviceName'] = BlueManage().connectDeviceName;
|
||||
|
||||
final List<String>? publicKey =
|
||||
await Storage.getStringList(saveBluePublicKey);
|
||||
final List<String>? publicKey = await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> publicKeyDataList = changeStringListToIntList(publicKey!);
|
||||
bluetooth['publicKey'] = publicKeyDataList;
|
||||
|
||||
final List<String>? privateKey =
|
||||
await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
|
||||
bluetooth['privateKey'] = getPrivateKeyList;
|
||||
|
||||
@ -410,8 +397,7 @@ class SaveLockLogic extends BaseGetXController {
|
||||
final String getMobile = (await Storage.getMobile())!;
|
||||
ApmHelper.instance.trackEvent('save_lock_result', {
|
||||
'lock_name': BlueManage().connectDeviceName,
|
||||
'account':
|
||||
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date': DateTool().getNowDateWithType(1),
|
||||
'save_lock_result': '成功',
|
||||
});
|
||||
@ -427,8 +413,7 @@ class SaveLockLogic extends BaseGetXController {
|
||||
final String getMobile = (await Storage.getMobile())!;
|
||||
ApmHelper.instance.trackEvent('save_lock_result', {
|
||||
'lock_name': BlueManage().connectDeviceName,
|
||||
'account':
|
||||
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date': DateTool().getNowDateWithType(1),
|
||||
'save_lock_result': '${entity.errorCode}--${entity.errorMsg}',
|
||||
});
|
||||
@ -489,26 +474,18 @@ class SaveLockLogic extends BaseGetXController {
|
||||
// BlueManage().disconnect();
|
||||
|
||||
// 查询锁设置信息
|
||||
final LockSetInfoEntity entity =
|
||||
await ApiRepository.to.getLockSettingInfoData(
|
||||
final LockSetInfoEntity entity = await ApiRepository.to.getLockSettingInfoData(
|
||||
lockId: state.lockId.toString(),
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
state.lockSetInfoData.value = entity.data!;
|
||||
if (state.lockSetInfoData.value.lockFeature?.wifi == 1) {
|
||||
// 如果是wifi锁,需要配置WIFI
|
||||
Get.toNamed(Routers.wifiListPage, arguments: {
|
||||
'lockSetInfoData': state.lockSetInfoData.value,
|
||||
'pageName': 'saveLock'
|
||||
});
|
||||
Get.toNamed(Routers.wifiListPage, arguments: {'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'
|
||||
});
|
||||
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)
|
||||
@ -518,15 +495,12 @@ class SaveLockLogic extends BaseGetXController {
|
||||
//刚刚配对完,需要对开锁页锁死 2 秒
|
||||
Future<void>.delayed(const Duration(milliseconds: 200), () {
|
||||
if (Get.isRegistered<LockDetailLogic>()) {
|
||||
Get.find<LockDetailLogic>()
|
||||
.functionBlocker
|
||||
.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
Get.find<LockDetailLogic>().functionBlocker.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
}
|
||||
});
|
||||
}
|
||||
} 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)
|
||||
@ -536,9 +510,7 @@ class SaveLockLogic extends BaseGetXController {
|
||||
//刚刚配对完,需要对开锁页锁死 2 秒
|
||||
Future<void>.delayed(const Duration(milliseconds: 200), () {
|
||||
if (Get.isRegistered<LockDetailLogic>()) {
|
||||
Get.find<LockDetailLogic>()
|
||||
.functionBlocker
|
||||
.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
Get.find<LockDetailLogic>().functionBlocker.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -22,8 +22,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
final GatewayConfigurationWifiState state = GatewayConfigurationWifiState();
|
||||
|
||||
Future<void> gatewayDistributionNetwork() async {
|
||||
final LoginEntity entity = await ApiRepository.to
|
||||
.gatewayDistributionNetwork(
|
||||
final LoginEntity entity = await ApiRepository.to.gatewayDistributionNetwork(
|
||||
gatewayName: state.gatewayNameTF.text,
|
||||
gatewayMac: state.gatewayModel.mac,
|
||||
serialNumber: state.gatewayModel.serialNum,
|
||||
@ -42,8 +41,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
Future<void> getGatewayConfiguration() async {
|
||||
final GetGatewayConfigurationEntity entity =
|
||||
await ApiRepository.to.getGatewayConfiguration(timeout: 60);
|
||||
final GetGatewayConfigurationEntity entity = await ApiRepository.to.getGatewayConfiguration(timeout: 60);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
String configStr = entity.data ?? '';
|
||||
|
||||
@ -52,6 +50,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
try {
|
||||
Map<String, dynamic> config = jsonDecode(configStr);
|
||||
config['timeZoneOffset'] = DateTime.now().timeZoneOffset.inSeconds;
|
||||
AppLog.log('state.config:$config');
|
||||
state.getGatewayConfigurationStr = jsonEncode(config);
|
||||
} catch (e) {
|
||||
AppLog.log('处理网关配置时区信息失败: $e');
|
||||
@ -68,9 +67,9 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
|
||||
// 监听设备返回的数据
|
||||
late StreamSubscription<Reply> _replySubscription;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) async {
|
||||
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) async {
|
||||
// WIFI配网
|
||||
// if(reply is GatewayConfiguringWifiReply) {
|
||||
// _replySenderConfiguringWifi(reply);
|
||||
@ -112,8 +111,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
cancelBlueConnetctToastTimer();
|
||||
dismissEasyLoading();
|
||||
final int secretKeyJsonLength = (reply.data[3] << 8) + reply.data[4];
|
||||
final List<int> secretKeyList =
|
||||
reply.data.sublist(5, 5 + secretKeyJsonLength);
|
||||
final List<int> secretKeyList = reply.data.sublist(5, 5 + secretKeyJsonLength);
|
||||
state.gatewayJson = utf8String(secretKeyList);
|
||||
|
||||
gatewayDistributionNetwork();
|
||||
@ -129,8 +127,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
|
||||
// 点击配置wifi
|
||||
Future<void> senderConfiguringWifiAction() async {
|
||||
AppLog.log(
|
||||
'state.getGatewayConfigurationStr:${state.getGatewayConfigurationStr}');
|
||||
AppLog.log('state.getGatewayConfigurationStr:${state.getGatewayConfigurationStr}');
|
||||
if (state.wifiNameTF.text.isEmpty) {
|
||||
showToast('请输入wifi名称'.tr);
|
||||
return;
|
||||
@ -158,8 +155,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
dismissEasyLoading();
|
||||
state.sureBtnState.value = 0;
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
IoSenderManage.gatewayConfiguringWifiCommand(
|
||||
ssid: state.wifiNameTF.text,
|
||||
@ -178,6 +174,7 @@ class GatewayConfigurationWifiLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
final NetworkInfo _networkInfo = NetworkInfo();
|
||||
|
||||
Future<String> getWifiName() async {
|
||||
String ssid = '';
|
||||
ssid = (await _networkInfo.getWifiName())!;
|
||||
|
||||
@ -305,6 +305,8 @@ abstract class Api {
|
||||
'/lockSetting/updateLockSetting'; // 设置语音包
|
||||
final String reportBuyRequestURL =
|
||||
'/service/reportBuyRequest'; // 上报增值服务购买请求
|
||||
final String getActivateInfoURL =
|
||||
final String getTppSupportURL =
|
||||
'/api/authCode/getTppSupport'; // 查询ttp
|
||||
final String getActivateInfoURL =
|
||||
'/api/authCode/getActivateInfo'; // 查询ttp
|
||||
}
|
||||
|
||||
@ -10,28 +10,48 @@ import 'package:star_lock/talk/starChart/entity/star_chart_register_node_entity.
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
|
||||
class StartCompanyApi extends BaseProvider {
|
||||
// 星图url
|
||||
// 星启url
|
||||
String _startChartHost = 'https://company.skychip.top';
|
||||
|
||||
static StartCompanyApi get to => Get.put(StartCompanyApi());
|
||||
|
||||
// 设置星图host
|
||||
// 设置星启host
|
||||
set startChartHost(String value) {
|
||||
_startChartHost = value;
|
||||
}
|
||||
|
||||
// 获取星图host
|
||||
// 获取星启host
|
||||
String get startChartHost => _startChartHost;
|
||||
|
||||
// ttp查询
|
||||
Future<ActivateInfoResponse> getActivateInfo({
|
||||
Future<TppSupportResponse> getTppSupport({
|
||||
required String model,
|
||||
}) async {
|
||||
final response = await post(
|
||||
_startChartHost + getTppSupportURL.toUrl,
|
||||
jsonEncode(<String, dynamic>{
|
||||
'model': model,
|
||||
}),
|
||||
isUnShowLoading: true,
|
||||
isUserBaseUrl: false,
|
||||
);
|
||||
return TppSupportResponse.fromJson(response.body);
|
||||
}
|
||||
|
||||
// 获取授权码
|
||||
Future<ActivateInfoResponse> getAuthorizationCode({
|
||||
required String registerKey,
|
||||
required String model,
|
||||
required String serialNum0,
|
||||
required int platform,
|
||||
}) async {
|
||||
final response = await post(
|
||||
_startChartHost + getActivateInfoURL.toUrl,
|
||||
jsonEncode(<String, dynamic>{
|
||||
'register_key': registerKey,
|
||||
'platform': platform,
|
||||
'model': model,
|
||||
|
||||
'serial_num0': serialNum0,
|
||||
}),
|
||||
isUnShowLoading: true,
|
||||
isUserBaseUrl: false,
|
||||
|
||||
@ -14,8 +14,7 @@ import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
|
||||
|
||||
// class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
// implements ScpMessageHandler {
|
||||
class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
implements ScpMessageHandler {
|
||||
class UdpTalkDataHandler extends ScpMessageBaseHandle implements ScpMessageHandler {
|
||||
// 单例实现
|
||||
static final UdpTalkDataHandler instance = UdpTalkDataHandler();
|
||||
|
||||
@ -73,10 +72,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
// _asyncLog(
|
||||
// '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength');
|
||||
if (messageType == MessageTypeConstant.RealTimeData) {
|
||||
if (spTotal != null &&
|
||||
spTotal > 1 &&
|
||||
messageId != null &&
|
||||
spIndex != null) {
|
||||
if (spTotal != null && spTotal > 1 && messageId != null && spIndex != null) {
|
||||
// 分包处理
|
||||
return handleFragmentedPayload(
|
||||
messageId: messageId,
|
||||
@ -129,13 +125,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
talkDataH264Frame.mergeFromBuffer(talkData.content);
|
||||
// AppLog.log('处理H264帧: frameType=${talkDataH264Frame.frameType}, frameSeq=${talkDataH264Frame.frameSeq},MessageId:${scpMessage.MessageId}');
|
||||
frameHandler.handleFrame(talkDataH264Frame, talkData, scpMessage);
|
||||
|
||||
}
|
||||
|
||||
/// 处理图片数据
|
||||
void _handleVideoImage(TalkData talkData) async {
|
||||
final List<Uint8List> processCompletePayload =
|
||||
await _processCompletePayload(Uint8List.fromList(talkData.content));
|
||||
final List<Uint8List> processCompletePayload = await _processCompletePayload(Uint8List.fromList(talkData.content));
|
||||
processCompletePayload.forEach((element) {
|
||||
talkData.content = element;
|
||||
talkDataRepository.addTalkData(
|
||||
@ -177,8 +171,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
startIdx = i;
|
||||
i++; // Skip the next byte
|
||||
} else if (nextByte == 0xD9 && startIdx != -1) {
|
||||
frames
|
||||
.add(Uint8List.view(payload.buffer, startIdx, i + 2 - startIdx));
|
||||
frames.add(Uint8List.view(payload.buffer, startIdx, i + 2 - startIdx));
|
||||
startIdx = -1;
|
||||
i++; // Skip the next byte
|
||||
}
|
||||
|
||||
@ -18,20 +18,18 @@ import 'package:star_lock/tools/push/xs_jPhush.dart';
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
import 'package:star_lock/translations/current_locale_tool.dart';
|
||||
|
||||
class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
implements ScpMessageHandler {
|
||||
RxString currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
class UdpTalkRequestHandler extends ScpMessageBaseHandle implements ScpMessageHandler {
|
||||
RxString currentLanguage = CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
|
||||
@override
|
||||
void handleReq(ScpMessage scpMessage) async {
|
||||
// 判断是否登录账户
|
||||
final loginData = await Storage.getLoginData();
|
||||
// 如果登录账户不为空,且不是被动接听状态,且不是接听成功状态
|
||||
if (loginData != null &&
|
||||
(talkStatus.status != TalkStatus.passiveCallWaitingAnswer ||
|
||||
talkStatus.status != TalkStatus.answeredSuccessfully)) {
|
||||
if (loginData != null && (talkStatus.status != TalkStatus.passiveCallWaitingAnswer || talkStatus.status != TalkStatus.answeredSuccessfully)) {
|
||||
// 收到对讲请求
|
||||
AppLog.log('收到对讲请求ToPeerId:${scpMessage.ToPeerId}');
|
||||
AppLog.log('收到对讲请求FromPeerId:${scpMessage.FromPeerId}');
|
||||
final TalkReq talkReq = scpMessage.Payload;
|
||||
startChartManage.FromPeerId = scpMessage.ToPeerId!;
|
||||
startChartManage.ToPeerId = scpMessage.FromPeerId!;
|
||||
@ -64,8 +62,6 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
_handleResponseSendExpect(
|
||||
lockPeerID: scpMessage.FromPeerId!,
|
||||
);
|
||||
// 发送预期数据
|
||||
startChartManage.startTalkExpectTimer();
|
||||
// 停止发送对讲请求
|
||||
startChartManage.stopCallRequestMessageTimer();
|
||||
// 收到应答后取消超时判断
|
||||
@ -97,8 +93,7 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
}
|
||||
|
||||
// 收到来电请求时进行本地通知
|
||||
Future<void> _showTalkRequestNotification(
|
||||
{required String talkObjectName}) async {
|
||||
Future<void> _showTalkRequestNotification({required String talkObjectName}) async {
|
||||
if (Platform.isAndroid) {
|
||||
final Map<String, dynamic> message = {
|
||||
'platform': 'all',
|
||||
@ -167,14 +162,12 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
void _handleRequestSendExpect({
|
||||
required String lockPeerID,
|
||||
}) async {
|
||||
final LockListInfoItemEntity currentKeyInfo =
|
||||
CommonDataManage().currentKeyInfo;
|
||||
final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo;
|
||||
|
||||
var isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
|
||||
var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
|
||||
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity =
|
||||
await Storage.getLockMainListData();
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity = await Storage.getLockMainListData();
|
||||
if (lockListInfoGroupEntity != null) {
|
||||
lockListInfoGroupEntity!.groupList?.forEach((element) {
|
||||
final lockList = element.lockList;
|
||||
@ -195,18 +188,15 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
if (isH264) {
|
||||
// 锁支持H264,发送H264视频和G711音频期望
|
||||
startChartManage.sendOnlyH264VideoTalkExpectData();
|
||||
print(
|
||||
'app收到的对讲请求后,发送的预期数据=========锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}');
|
||||
print('app收到的对讲请求后,发送的预期数据=========锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}');
|
||||
} else if (isMJpeg) {
|
||||
// 锁只支持MJPEG,发送图像视频和G711音频期望
|
||||
startChartManage.sendOnlyImageVideoTalkExpectData();
|
||||
print(
|
||||
'app收到的对讲请求后,发送的预期数据=========锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
print('app收到的对讲请求后,发送的预期数据=========锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
} else {
|
||||
// 默认使用图像视频
|
||||
startChartManage.sendOnlyImageVideoTalkExpectData();
|
||||
print(
|
||||
'app收到的对讲请求后,发送的预期数据=========锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
print('app收到的对讲请求后,发送的预期数据=========锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,14 +204,12 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
void _handleResponseSendExpect({
|
||||
required String lockPeerID,
|
||||
}) async {
|
||||
final LockListInfoItemEntity currentKeyInfo =
|
||||
CommonDataManage().currentKeyInfo;
|
||||
final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo;
|
||||
|
||||
var isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
|
||||
var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
|
||||
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity =
|
||||
await Storage.getLockMainListData();
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity = await Storage.getLockMainListData();
|
||||
if (lockListInfoGroupEntity != null) {
|
||||
lockListInfoGroupEntity!.groupList?.forEach((element) {
|
||||
final lockList = element.lockList;
|
||||
@ -242,18 +230,15 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
if (isH264) {
|
||||
// 锁支持H264,发送H264视频和G711音频期望
|
||||
startChartManage.sendH264VideoAndG711AudioTalkExpectData();
|
||||
AppLog.log(
|
||||
'app主动发对讲请求,收到回复后发送的预期数据=======锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}');
|
||||
AppLog.log('app主动发对讲请求,收到回复后发送的预期数据=======锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}');
|
||||
} else if (isMJpeg) {
|
||||
// 锁只支持MJPEG,发送图像视频和G711音频期望
|
||||
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
|
||||
AppLog.log(
|
||||
'app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
AppLog.log('app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
} else {
|
||||
// 默认使用图像视频
|
||||
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
|
||||
AppLog.log(
|
||||
'app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
AppLog.log('app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,12 +61,9 @@ class StartChartManage {
|
||||
|
||||
// 单例对象
|
||||
static final StartChartManage _instance = StartChartManage._internal();
|
||||
final TalkeRequestOverTimeTimerManager talkeRequestOverTimeTimerManager =
|
||||
TalkeRequestOverTimeTimerManager();
|
||||
final TalkePingOverTimeTimerManager talkePingOverTimeTimerManager =
|
||||
TalkePingOverTimeTimerManager();
|
||||
final TalkDataOverTimeTimerManager talkDataOverTimeTimerManager =
|
||||
TalkDataOverTimeTimerManager();
|
||||
final TalkeRequestOverTimeTimerManager talkeRequestOverTimeTimerManager = TalkeRequestOverTimeTimerManager();
|
||||
final TalkePingOverTimeTimerManager talkePingOverTimeTimerManager = TalkePingOverTimeTimerManager();
|
||||
final TalkDataOverTimeTimerManager talkDataOverTimeTimerManager = TalkDataOverTimeTimerManager();
|
||||
|
||||
// 工厂构造函数,返回单例对象
|
||||
factory StartChartManage() {
|
||||
@ -171,8 +168,7 @@ class StartChartManage {
|
||||
FromPeerId = loginData?.starchart?.starchartId ?? '';
|
||||
} else {
|
||||
_log(text: '开始注册客户端');
|
||||
final StarChartRegisterNodeEntity requestStarChartRegisterNode =
|
||||
await _requestStarChartRegisterNode();
|
||||
final StarChartRegisterNodeEntity requestStarChartRegisterNode = await _requestStarChartRegisterNode();
|
||||
await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode);
|
||||
FromPeerId = requestStarChartRegisterNode.peer!.id ?? '';
|
||||
bindUserStarchart(requestStarChartRegisterNode);
|
||||
@ -180,18 +176,14 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
//绑定星图配置
|
||||
Future<void> bindUserStarchart(
|
||||
StarChartRegisterNodeEntity requestStarChartRegisterNode) async {
|
||||
Future<void> bindUserStarchart(StarChartRegisterNodeEntity requestStarChartRegisterNode) async {
|
||||
try {
|
||||
final LoginEntity entity = await ApiRepository.to.bindUserStarchart(
|
||||
starchartId: requestStarChartRegisterNode.peer?.id ?? '',
|
||||
starchartPeerPublicKey:
|
||||
requestStarChartRegisterNode.peer?.publicKey ?? '',
|
||||
starchartPeerPrivateKey:
|
||||
requestStarChartRegisterNode.peer?.privateKey ?? '',
|
||||
starchartPeerPublicKey: requestStarChartRegisterNode.peer?.publicKey ?? '',
|
||||
starchartPeerPrivateKey: requestStarChartRegisterNode.peer?.privateKey ?? '',
|
||||
);
|
||||
requestStarChartRegisterNode.peer?.id =
|
||||
entity.data?.starchart?.starchartId;
|
||||
requestStarChartRegisterNode.peer?.id = entity.data?.starchart?.starchartId;
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
AppLog.log('绑定成功');
|
||||
} else {
|
||||
@ -204,14 +196,12 @@ class StartChartManage {
|
||||
|
||||
// 中继查询
|
||||
Future<void> _relayQuery() async {
|
||||
final RelayInfoEntity relayInfoEntity =
|
||||
await StartChartApi.to.relayQueryInfo();
|
||||
final RelayInfoEntity relayInfoEntity = await StartChartApi.to.relayQueryInfo();
|
||||
_saveRelayInfoEntityToStorage(relayInfoEntity);
|
||||
if (relayInfoEntity.client_addr != null) {
|
||||
localPublicHost = relayInfoEntity.client_addr!;
|
||||
}
|
||||
if (relayInfoEntity.relay_list != null &&
|
||||
relayInfoEntity.relay_list!.length > 0) {
|
||||
if (relayInfoEntity.relay_list != null && relayInfoEntity.relay_list!.length > 0) {
|
||||
for (int i = 0; i <= relayInfoEntity.relay_list!.length; i++) {
|
||||
final data = relayInfoEntity.relay_list?[i];
|
||||
if (data?.peerID != FromPeerId) {
|
||||
@ -239,8 +229,7 @@ class StartChartManage {
|
||||
// 初始化udp
|
||||
Future<void> _onlineRelayService() async {
|
||||
var addressIListenFrom = InternetAddress.anyIPv4;
|
||||
await RawDatagramSocket.bind(addressIListenFrom, localPort)
|
||||
.then((RawDatagramSocket socket) {
|
||||
await RawDatagramSocket.bind(addressIListenFrom, localPort).then((RawDatagramSocket socket) {
|
||||
// 设置接收缓冲区大小 (SO_RCVBUF = 8)
|
||||
if (AppPlatform.isAndroid) {
|
||||
socket.setRawOption(
|
||||
@ -291,15 +280,15 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送RbcuInfo 地址交换消息
|
||||
void _sendRbcuInfoMessage(
|
||||
{required String ToPeerId, bool isResp = false}) async {
|
||||
void _sendRbcuInfoMessage({required String ToPeerId, bool isResp = false}) async {
|
||||
final uuid = _uuid.v1();
|
||||
final int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final int timestamp = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch;
|
||||
final Int64 int64Timestamp = Int64(timestamp); // 使用构造函数
|
||||
|
||||
// 获取本机所有ip地址和中继返回的外网地址
|
||||
final List<ListenAddrData> listenAddrDataList =
|
||||
await _makeListenAddrDataList();
|
||||
final List<ListenAddrData> listenAddrDataList = await _makeListenAddrDataList();
|
||||
listenAddrDataList.insert(
|
||||
0, // 插入到头部
|
||||
ListenAddrData(
|
||||
@ -309,15 +298,13 @@ class StartChartManage {
|
||||
);
|
||||
|
||||
final address = listenAddrDataList
|
||||
.where((element) =>
|
||||
element.type == ListenAddrTypeConstant.local) // 过滤出本地地址
|
||||
.where((element) => element.type == ListenAddrTypeConstant.local) // 过滤出本地地址
|
||||
.map((e) => e.address) // 转换为 List<String?>
|
||||
.where((addr) => addr != null) // 过滤掉 null 值
|
||||
.map(
|
||||
(addr) => addr!.replaceAll(IpConstant.udpUrl, ''),
|
||||
) // 去除 "udp://" 前缀
|
||||
.cast<
|
||||
String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
|
||||
.cast<String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
|
||||
_rbcuSessionId = uuid;
|
||||
final RbcuInfo rbcuInfo = RbcuInfo(
|
||||
sessionId: uuid,
|
||||
@ -340,28 +327,21 @@ class StartChartManage {
|
||||
void _sendRbcuProbeMessage() async {
|
||||
// 随机字符串数据
|
||||
String generateRandomString(int length) {
|
||||
const chars =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
final random = Random();
|
||||
return String.fromCharCodes(
|
||||
List.generate(
|
||||
length, (index) => chars.codeUnitAt(random.nextInt(chars.length))),
|
||||
List.generate(length, (index) => chars.codeUnitAt(random.nextInt(chars.length))),
|
||||
);
|
||||
}
|
||||
|
||||
if (rbcuInfo != null &&
|
||||
rbcuInfo!.address != null &&
|
||||
rbcuInfo!.address.length > 0) {
|
||||
if (rbcuInfo != null && rbcuInfo!.address != null && rbcuInfo!.address.length > 0) {
|
||||
rbcuInfo!.address.forEach((element) {
|
||||
// 拆分 element 字符串
|
||||
final parts = element.split(':');
|
||||
final host = parts[0]; // IP 地址
|
||||
final port = int.tryParse(parts[1]) ?? 0; // 端口号,如果解析失败则默认为 0
|
||||
|
||||
final RbcuProbe rbcuProbe = RbcuProbe(
|
||||
sessionId: _rbcuSessionId,
|
||||
data: generateRandomString(100),
|
||||
targetAddress: element);
|
||||
final RbcuProbe rbcuProbe = RbcuProbe(sessionId: _rbcuSessionId, data: generateRandomString(100), targetAddress: element);
|
||||
final rbcuProBeBuffer = rbcuProbe.writeToBuffer();
|
||||
_sendNatMessage(message: rbcuProBeBuffer, host: host, port: port);
|
||||
});
|
||||
@ -378,8 +358,7 @@ class StartChartManage {
|
||||
// 启动定时任务
|
||||
void startSendingRbcuInfoMessages({required String ToPeerId}) {
|
||||
// 每隔 1 秒执行一次 _sendRbcuInfoMessage
|
||||
rbcuInfoTimer ??=
|
||||
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
rbcuInfoTimer ??= Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
// 发送RbcuInfo 地址交换消息
|
||||
_log(text: '发送RbcuInfo 地址交换消息');
|
||||
_sendRbcuInfoMessage(ToPeerId: ToPeerId);
|
||||
@ -389,8 +368,7 @@ class StartChartManage {
|
||||
// 发送打洞包
|
||||
void startSendingRbcuProbeTMessages() {
|
||||
// 每隔 1 秒执行一次 _sendRbcuInfoMessage
|
||||
rbcuProbeTimer ??=
|
||||
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
rbcuProbeTimer ??= Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
// 发送RbcuProbe
|
||||
_sendRbcuProbeMessage();
|
||||
});
|
||||
@ -398,8 +376,7 @@ class StartChartManage {
|
||||
|
||||
// 发送打洞确认包
|
||||
void startSendingRbcuConfirmTMessages() {
|
||||
rbcuConfirmTimer ??=
|
||||
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
rbcuConfirmTimer ??= Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
// 发送RbcuProbe
|
||||
_sendRbcuConfirmMessage();
|
||||
});
|
||||
@ -458,11 +435,11 @@ class StartChartManage {
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
final LockListInfoItemEntity currentKeyInfo =
|
||||
CommonDataManage().currentKeyInfo;
|
||||
final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo;
|
||||
final isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
|
||||
final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
|
||||
|
||||
AppLog.log('isH264:${isH264}');
|
||||
AppLog.log('isMJpeg:${isMJpeg}');
|
||||
// 优先使用H264,其次是MJPEG
|
||||
if (isH264) {
|
||||
Get.toNamed(
|
||||
@ -483,6 +460,7 @@ class StartChartManage {
|
||||
seconds: _defaultIntervalTime,
|
||||
),
|
||||
(Timer timer) async {
|
||||
AppLog.log('发送对讲请求:${ToPeerId}');
|
||||
await sendCallRequestMessage(ToPeerId: ToPeerId);
|
||||
},
|
||||
);
|
||||
@ -526,8 +504,7 @@ class StartChartManage {
|
||||
List<int> packetTalkData = payload.sublist(start, end);
|
||||
|
||||
// 分包数据不递增messageID
|
||||
final messageId =
|
||||
MessageCommand.getNextMessageId(toPeerId, increment: false);
|
||||
final messageId = MessageCommand.getNextMessageId(toPeerId, increment: false);
|
||||
// 组装分包数据
|
||||
final message = MessageCommand.talkDataMessage(
|
||||
ToPeerId: toPeerId,
|
||||
@ -562,8 +539,7 @@ class StartChartManage {
|
||||
final List<int> message = MessageCommand.heartbeatMessage(
|
||||
FromPeerId: FromPeerId,
|
||||
ToPeerId: relayPeerId,
|
||||
MessageId:
|
||||
MessageCommand.getNextMessageId(relayPeerId, increment: true),
|
||||
MessageId: MessageCommand.getNextMessageId(relayPeerId, increment: true),
|
||||
);
|
||||
await _sendMessage(message: message);
|
||||
},
|
||||
@ -572,8 +548,7 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送回声测试消息
|
||||
void sendEchoMessage(
|
||||
{required List<int> payload, required String toPeerId}) async {
|
||||
void sendEchoMessage({required List<int> payload, required String toPeerId}) async {
|
||||
// 计算需要分多少个包发送
|
||||
final int totalPackets = (payload.length / _maxPayloadSize).ceil();
|
||||
// 循环遍历
|
||||
@ -587,8 +562,7 @@ class StartChartManage {
|
||||
List<int> packet = payload.sublist(start, end);
|
||||
|
||||
// 分包数据不递增messageID
|
||||
final messageId =
|
||||
MessageCommand.getNextMessageId(toPeerId, increment: false);
|
||||
final messageId = MessageCommand.getNextMessageId(toPeerId, increment: false);
|
||||
// 组装分包数据
|
||||
final message = MessageCommand.echoMessage(
|
||||
ToPeerId: toPeerId,
|
||||
@ -606,13 +580,14 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送网关初始化消息
|
||||
void sendGatewayResetMessage(
|
||||
{required String ToPeerId, required int gatewayId}) async {
|
||||
void sendGatewayResetMessage({required String ToPeerId, required int gatewayId}) async {
|
||||
final message = MessageCommand.gatewayResetMessage(
|
||||
ToPeerId: ToPeerId,
|
||||
FromPeerId: FromPeerId,
|
||||
gatewayId: gatewayId,
|
||||
time: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
time: DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch ~/ 1000,
|
||||
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
|
||||
);
|
||||
await _sendMessage(message: message);
|
||||
@ -728,8 +703,7 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送通话保持消息
|
||||
Future<void> sendTalkPingMessage(
|
||||
{required String ToPeerId, required String FromPeerId}) async {
|
||||
Future<void> sendTalkPingMessage({required String ToPeerId, required String FromPeerId}) async {
|
||||
final message = MessageCommand.talkPingMessage(
|
||||
ToPeerId: ToPeerId,
|
||||
FromPeerId: FromPeerId,
|
||||
@ -825,11 +799,9 @@ class StartChartManage {
|
||||
|
||||
// 发送消息
|
||||
Future<void> _sendMessage({required List<int> message}) async {
|
||||
var result = await _udpSocket?.send(
|
||||
message, InternetAddress(remoteHost), remotePort);
|
||||
var result = await _udpSocket?.send(message, InternetAddress(remoteHost), remotePort);
|
||||
if (result != message.length) {
|
||||
throw StartChartMessageException(
|
||||
'❌Udp send data error----> $result ${message.length}');
|
||||
throw StartChartMessageException('❌Udp send data error----> $result ${message.length}');
|
||||
// _udpSocket = null;
|
||||
}
|
||||
|
||||
@ -846,15 +818,11 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
Future<void> _sendNatMessage(
|
||||
{required List<int> message,
|
||||
required String host,
|
||||
required int port}) async {
|
||||
Future<void> _sendNatMessage({required List<int> message, required String host, required int port}) async {
|
||||
_log(text: '发送nat消息');
|
||||
var result = await _udpSocket?.send(message, InternetAddress(host), port);
|
||||
if (result != message.length) {
|
||||
throw StartChartMessageException(
|
||||
'❌Udp send data error----> $result ${message.length}');
|
||||
throw StartChartMessageException('❌Udp send data error----> $result ${message.length}');
|
||||
// _udpSocket = null;
|
||||
}
|
||||
}
|
||||
@ -864,8 +832,7 @@ class StartChartManage {
|
||||
// 获取设备信息
|
||||
final Map<String, String> deviceInfo = await _getDeviceInfo();
|
||||
// 发送注册节点请求
|
||||
final StarChartRegisterNodeEntity response =
|
||||
await StartChartApi.to.starChartRegisterNode(
|
||||
final StarChartRegisterNodeEntity response = await StartChartApi.to.starChartRegisterNode(
|
||||
product: _productName,
|
||||
model: '${deviceInfo['brand']}_${deviceInfo['model']}',
|
||||
name: '${deviceInfo['id']}',
|
||||
@ -875,8 +842,7 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 保存星图注册节点信息至缓存
|
||||
Future<void> _saveStarChartRegisterNodeToStorage(
|
||||
StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
|
||||
Future<void> _saveStarChartRegisterNodeToStorage(StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
|
||||
if (starChartRegisterNodeEntity != null) {
|
||||
await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity);
|
||||
final LoginData? loginData = await Storage.getLoginData();
|
||||
@ -886,8 +852,7 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 保存星图中继服务器信息至缓存
|
||||
Future<void> _saveRelayInfoEntityToStorage(
|
||||
RelayInfoEntity relayInfoEntity) async {
|
||||
Future<void> _saveRelayInfoEntityToStorage(RelayInfoEntity relayInfoEntity) async {
|
||||
if (relayInfoEntity != null) {
|
||||
await Storage.saveRelayInfo(relayInfoEntity);
|
||||
}
|
||||
@ -909,8 +874,7 @@ class StartChartManage {
|
||||
);
|
||||
|
||||
// 获取本机所有ip地址和中继返回的外网地址
|
||||
final List<ListenAddrData> listenAddrDataList =
|
||||
await _makeListenAddrDataList();
|
||||
final List<ListenAddrData> listenAddrDataList = await _makeListenAddrDataList();
|
||||
|
||||
//
|
||||
final RelayServiceData relayServiceData = RelayServiceData(
|
||||
@ -977,11 +941,12 @@ class StartChartManage {
|
||||
String ipAddress = address.address;
|
||||
// 移除 IPv6 地址中的接口标识符(如果有)
|
||||
if (ipAddress.contains('%')) {
|
||||
ipAddress = ipAddress.split('%').first;
|
||||
ipAddress = ipAddress
|
||||
.split('%')
|
||||
.first;
|
||||
}
|
||||
// 确保 IP 地址不为空且不在排除列表中
|
||||
if (ipAddress.isNotEmpty &&
|
||||
!IpConstant.reportExcludeIp.contains(ipAddress)) {
|
||||
if (ipAddress.isNotEmpty && !IpConstant.reportExcludeIp.contains(ipAddress)) {
|
||||
ipAddresses.add(ipAddress);
|
||||
}
|
||||
}
|
||||
@ -998,8 +963,7 @@ class StartChartManage {
|
||||
|
||||
/// 获取设备信息
|
||||
Future<Map<String, String>> _getDeviceInfo() async {
|
||||
final Map<String, String> deviceInfo =
|
||||
await DeviceInfoUtils.getDeviceInfo();
|
||||
final Map<String, String> deviceInfo = await DeviceInfoUtils.getDeviceInfo();
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
@ -1016,8 +980,7 @@ class StartChartManage {
|
||||
if (host != null && port != null) {
|
||||
try {
|
||||
// 尝试进行 DNS 解析
|
||||
final List<InternetAddress> addresses =
|
||||
await InternetAddress.lookup(host);
|
||||
final List<InternetAddress> addresses = await InternetAddress.lookup(host);
|
||||
if (addresses.isEmpty) {
|
||||
throw FormatException('DNS resolution failed for $host');
|
||||
}
|
||||
@ -1082,8 +1045,7 @@ class StartChartManage {
|
||||
final int payloadType = scpMessage.PayloadType ?? 0;
|
||||
final int messageType = scpMessage.MessageType ?? 0;
|
||||
try {
|
||||
final ScpMessageHandler handler =
|
||||
ScpMessageHandlerFactory.createHandler(payloadType);
|
||||
final ScpMessageHandler handler = ScpMessageHandlerFactory.createHandler(payloadType);
|
||||
if (messageType == MessageTypeConstant.Req) {
|
||||
handler.handleReq(scpMessage);
|
||||
} else if (messageType == MessageTypeConstant.Resp) {
|
||||
@ -1170,10 +1132,18 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
void changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
{required TalkExpectReq talkExpect}) {
|
||||
void changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer({required TalkExpectReq talkExpect}) {
|
||||
_defaultTalkExpect = talkExpect;
|
||||
reStartTalkExpectMessageTimer();
|
||||
sendTalkExpectMessage(
|
||||
talkExpect: _defaultTalkExpect,
|
||||
);
|
||||
sendTalkExpectMessage(
|
||||
talkExpect: _defaultTalkExpect,
|
||||
);
|
||||
sendTalkExpectMessage(
|
||||
talkExpect: _defaultTalkExpect,
|
||||
);
|
||||
// reStartTalkExpectMessageTimer();
|
||||
}
|
||||
|
||||
void reSetDefaultTalkExpect() {
|
||||
@ -1190,8 +1160,7 @@ class StartChartManage {
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [],
|
||||
);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: talkExpectReq);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq);
|
||||
}
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
@ -1200,20 +1169,17 @@ class StartChartManage {
|
||||
videoType: [VideoTypeE.H264],
|
||||
audioType: [],
|
||||
);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: talkExpectReq);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq);
|
||||
}
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
void sendImageVideoAndG711AudioTalkExpectData() {
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: TalkConstant.ImageExpect);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: TalkConstant.ImageExpect);
|
||||
}
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
void sendH264VideoAndG711AudioTalkExpectData() {
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: TalkConstant.H264Expect);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: TalkConstant.H264Expect);
|
||||
}
|
||||
|
||||
/// 发送远程开锁
|
||||
|
||||
@ -18,7 +18,7 @@ class _PermissionGuidancePageState extends State<PermissionGuidancePage> {
|
||||
|
||||
final List<Map<String, String>> _stepsData = [
|
||||
{
|
||||
'image': 'images/guide/matter.png',
|
||||
'image': 'images/guide/1.png',
|
||||
'text': '步骤1:打开应用信息,点击通知管理选项',
|
||||
},
|
||||
{
|
||||
@ -26,7 +26,7 @@ class _PermissionGuidancePageState extends State<PermissionGuidancePage> {
|
||||
'text': '步骤2:下滑点击呼叫提醒的通知选项',
|
||||
},
|
||||
{
|
||||
'image': 'images/guide/tuya.png',
|
||||
'image': 'images/guide/3.png',
|
||||
'text': '步骤3:选择在锁定屏幕上的选项设置',
|
||||
},
|
||||
{
|
||||
|
||||
@ -104,10 +104,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
codecType: 'h264',
|
||||
);
|
||||
// 初始化解码器并获取textureId
|
||||
AppLog.log(
|
||||
'StartChartManage().videoWidth:${StartChartManage().videoWidth}');
|
||||
AppLog.log(
|
||||
'StartChartManage().videoHeight:${StartChartManage().videoHeight}');
|
||||
final textureId = await VideoDecodePlugin.initDecoder(config);
|
||||
if (textureId != null) {
|
||||
Future.microtask(() => state.textureId.value = textureId);
|
||||
@ -172,15 +168,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
ScpMessage scpMessage,
|
||||
) {
|
||||
// 动态回绕阈值判断,frameSeq较小时阈值也小
|
||||
if (!_pendingStreamReset &&
|
||||
_lastFrameSeq != null &&
|
||||
frameType == TalkDataH264Frame_FrameTypeE.I &&
|
||||
frameSeq < _lastFrameSeq!) {
|
||||
if (!_pendingStreamReset && _lastFrameSeq != null && frameType == TalkDataH264Frame_FrameTypeE.I && frameSeq < _lastFrameSeq!) {
|
||||
int dynamicThreshold = _getFrameSeqRolloverThreshold(_lastFrameSeq!);
|
||||
if ((_lastFrameSeq! - frameSeq) > dynamicThreshold) {
|
||||
// 检测到新流I帧,frameSeq大幅回绕,进入loading并重置所有本地状态
|
||||
AppLog.log(
|
||||
'检测到新流I帧,frameSeq大幅回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq, 阈值=$dynamicThreshold');
|
||||
AppLog.log('检测到新流I帧,frameSeq大幅回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq, 阈值=$dynamicThreshold');
|
||||
Future.microtask(() => state.isLoading.value = true);
|
||||
_pendingStreamReset = true;
|
||||
// 先暂停帧处理定时器,防止竞态
|
||||
@ -197,8 +189,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
// 继续往下执行
|
||||
} else {
|
||||
// 小幅度乱序,直接丢弃
|
||||
AppLog.log(
|
||||
'检测到I帧乱序(未超过回绕阈值$dynamicThreshold),丢弃: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq');
|
||||
AppLog.log('检测到I帧乱序(未超过回绕阈值$dynamicThreshold),丢弃: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -238,8 +229,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
// 如果缓冲区超出最大大小,优先丢弃P/B帧
|
||||
while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
||||
int pbIndex = state.h264FrameBuffer
|
||||
.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||
if (pbIndex != -1) {
|
||||
state.h264FrameBuffer.removeAt(pbIndex);
|
||||
} else {
|
||||
@ -260,15 +250,17 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
final int intervalMs = (1000 / state.targetFps).round();
|
||||
|
||||
// 创建新定时器
|
||||
state.frameProcessTimer =
|
||||
Timer.periodic(Duration(milliseconds: intervalMs), (timer) {
|
||||
state.frameProcessTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) {
|
||||
_processNextFrameFromBuffer();
|
||||
});
|
||||
AppLog.log('启动帧处理定时器,目标帧率: ${state.targetFps}fps,间隔: ${intervalMs}ms');
|
||||
}
|
||||
|
||||
/// 从缓冲区处理下一帧
|
||||
/// 从缓冲区处理下一帧
|
||||
void _processNextFrameFromBuffer() async {
|
||||
final startTime = DateTime.now().microsecondsSinceEpoch;
|
||||
|
||||
// 避免重复处理
|
||||
if (state.isProcessingFrame) {
|
||||
return;
|
||||
@ -279,25 +271,19 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 优先查找I帧,按frameSeq最小的I帧消费
|
||||
final iFrames = state.h264FrameBuffer
|
||||
.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I)
|
||||
.toList();
|
||||
iFrames
|
||||
.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
final iFrames = state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I).toList();
|
||||
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
|
||||
if (iFrames.isNotEmpty) {
|
||||
// 有I帧,消费最小的I帧,并记录其frameSeq
|
||||
final minIFrame = iFrames.first;
|
||||
final minIFrameSeq = minIFrame['frameSeq'];
|
||||
final targetIndex = state.h264FrameBuffer.indexWhere(
|
||||
(f) =>
|
||||
f['frameType'] == TalkDataH264Frame_FrameTypeE.I &&
|
||||
f['frameSeq'] == minIFrameSeq,
|
||||
(f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I && f['frameSeq'] == minIFrameSeq,
|
||||
);
|
||||
state.isProcessingFrame = true;
|
||||
final Map<String, dynamic>? frameMap =
|
||||
state.h264FrameBuffer.removeAt(targetIndex);
|
||||
final Map<String, dynamic>? frameMap = state.h264FrameBuffer.removeAt(targetIndex);
|
||||
if (frameMap == null) {
|
||||
state.isProcessingFrame = false;
|
||||
return;
|
||||
@ -307,12 +293,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
final int? frameSeq = frameMap['frameSeq'];
|
||||
final int? frameSeqI = frameMap['frameSeqI'];
|
||||
final int? pts = frameMap['pts'];
|
||||
final ScpMessage? scpMessage = frameMap['scpMessage'];
|
||||
if (frameData == null ||
|
||||
frameType == null ||
|
||||
frameSeq == null ||
|
||||
frameSeqI == null ||
|
||||
pts == null) {
|
||||
if (frameData == null || frameType == null || frameSeq == null || frameSeqI == null || pts == null) {
|
||||
state.isProcessingFrame = false;
|
||||
return;
|
||||
}
|
||||
@ -321,11 +302,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
return;
|
||||
}
|
||||
lastDecodedIFrameSeq = minIFrameSeq;
|
||||
// AppLog.log('送入解码器的P帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},'
|
||||
// 'frameType:${frameType},messageId:${scpMessage!.MessageId}');
|
||||
// final spsData = NaluUtils.filterNalusByType(frameData, 7);
|
||||
// final ppsData = NaluUtils.filterNalusByType(frameData, 8);
|
||||
// AppLog.log('SPSDATA:${spsData},ppsData:${ppsData}');
|
||||
|
||||
await VideoDecodePlugin.sendFrame(
|
||||
frameData: frameData,
|
||||
frameType: 0,
|
||||
@ -340,24 +317,17 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
// 没有I帧时,只消费refIFrameSeq等于lastDecodedIFrameSeq的P帧
|
||||
if (lastDecodedIFrameSeq != null) {
|
||||
final validPFrames = state.h264FrameBuffer
|
||||
.where((f) =>
|
||||
f['frameType'] == TalkDataH264Frame_FrameTypeE.P &&
|
||||
f['frameSeqI'] == lastDecodedIFrameSeq)
|
||||
.toList();
|
||||
final validPFrames =
|
||||
state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeqI'] == lastDecodedIFrameSeq).toList();
|
||||
if (validPFrames.isNotEmpty) {
|
||||
validPFrames.sort(
|
||||
(a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
validPFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
final minPFrame = validPFrames.first;
|
||||
final targetIndex = state.h264FrameBuffer.indexWhere(
|
||||
(f) =>
|
||||
f['frameType'] == TalkDataH264Frame_FrameTypeE.P &&
|
||||
f['frameSeq'] == minPFrame['frameSeq'] &&
|
||||
f['frameSeqI'] == lastDecodedIFrameSeq,
|
||||
f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeq'] == minPFrame['frameSeq'] && f['frameSeqI'] == lastDecodedIFrameSeq,
|
||||
);
|
||||
state.isProcessingFrame = true;
|
||||
final Map<String, dynamic>? frameMap =
|
||||
state.h264FrameBuffer.removeAt(targetIndex);
|
||||
final Map<String, dynamic>? frameMap = state.h264FrameBuffer.removeAt(targetIndex);
|
||||
if (frameMap == null) {
|
||||
state.isProcessingFrame = false;
|
||||
return;
|
||||
@ -367,12 +337,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
final int? frameSeq = frameMap['frameSeq'];
|
||||
final int? frameSeqI = frameMap['frameSeqI'];
|
||||
final int? pts = frameMap['pts'];
|
||||
final ScpMessage? scpMessage = frameMap['scpMessage'];
|
||||
if (frameData == null ||
|
||||
frameType == null ||
|
||||
frameSeq == null ||
|
||||
frameSeqI == null ||
|
||||
pts == null) {
|
||||
if (frameData == null || frameType == null || frameSeq == null || frameSeqI == null || pts == null) {
|
||||
state.isProcessingFrame = false;
|
||||
return;
|
||||
}
|
||||
@ -380,8 +345,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
state.isProcessingFrame = false;
|
||||
return;
|
||||
}
|
||||
// AppLog.log('送入解码器的I帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},'
|
||||
// 'frameType:${frameType},messageId:${scpMessage!.MessageId}');
|
||||
|
||||
await VideoDecodePlugin.sendFrame(
|
||||
frameData: frameData,
|
||||
@ -396,6 +359,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
// 其他情况不消费,等待I帧到来
|
||||
} finally {
|
||||
final endTime = DateTime.now().microsecondsSinceEpoch;
|
||||
final durationMs = (endTime - startTime) / 1000.0;
|
||||
// 可选:只在耗时较长时打印(例如 > 5ms)
|
||||
if (durationMs > 5) {
|
||||
debugPrint('[_processNextFrameFromBuffer] 耗时: ${durationMs.toStringAsFixed(2)} ms');
|
||||
// 或使用你的日志系统,如:
|
||||
// AppLog.log('Frame processing took ${durationMs.toStringAsFixed(2)} ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止帧处理定时器
|
||||
@ -427,8 +400,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
AppLog.log("==== 启动新的数据流监听 ====");
|
||||
_isListening = true;
|
||||
|
||||
_streamSubscription = state.talkDataRepository.talkDataStream
|
||||
.listen((TalkDataModel talkDataModel) async {
|
||||
_streamSubscription = state.talkDataRepository.talkDataStream.listen((TalkDataModel talkDataModel) async {
|
||||
_processFrame(talkDataModel);
|
||||
});
|
||||
}
|
||||
@ -437,8 +409,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
void _playAudioFrames() {
|
||||
// 如果缓冲区为空或未达到目标大小,不进行播放
|
||||
// 音频缓冲区要求更小,以减少延迟
|
||||
if (state.audioBuffer.isEmpty ||
|
||||
state.audioBuffer.length < audioBufferSize) {
|
||||
if (state.audioBuffer.isEmpty || state.audioBuffer.length < audioBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -446,8 +417,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
TalkData? oldestFrame;
|
||||
int oldestIndex = -1;
|
||||
for (int i = 0; i < state.audioBuffer.length; i++) {
|
||||
if (oldestFrame == null ||
|
||||
state.audioBuffer[i].durationMs < oldestFrame.durationMs) {
|
||||
if (oldestFrame == null || state.audioBuffer[i].durationMs < oldestFrame.durationMs) {
|
||||
oldestFrame = state.audioBuffer[i];
|
||||
oldestIndex = i;
|
||||
}
|
||||
@ -477,8 +447,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
break;
|
||||
case TalkStatus.answeredSuccessfully:
|
||||
state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器
|
||||
state.oneMinuteTimeTimer ??=
|
||||
Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
||||
state.oneMinuteTimeTimer ??= Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
||||
if (state.isLoading.isFalse) {
|
||||
state.oneMinuteTime.value++;
|
||||
}
|
||||
@ -493,9 +462,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
/// 播放音频数据
|
||||
void _playAudioData(TalkData talkData) async {
|
||||
if (state.isOpenVoice.value &&
|
||||
state.isLoading.isFalse &&
|
||||
state.isRecordingAudio.value == false) {
|
||||
if (state.isOpenVoice.value && state.isLoading.isFalse && state.isRecordingAudio.value == false) {
|
||||
List<int> encodedData = G711Tool.decode(talkData.content, 0); // 0表示A-law
|
||||
// 将 PCM 数据转换为 PcmArrayInt16
|
||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(encodedData);
|
||||
@ -670,11 +637,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
AppLog.log('截图失败: 未找到当前上下文');
|
||||
return;
|
||||
}
|
||||
final RenderRepaintBoundary boundary = state.globalKey.currentContext!
|
||||
.findRenderObject()! as RenderRepaintBoundary;
|
||||
final RenderRepaintBoundary boundary = state.globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||||
final ui.Image image = await boundary.toImage();
|
||||
final ByteData? byteData =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
|
||||
if (byteData == null) {
|
||||
AppLog.log('截图失败: 图像数据为空');
|
||||
@ -702,15 +667,13 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
// 远程开锁
|
||||
Future<void> remoteOpenLock() async {
|
||||
final LockListInfoItemEntity currentKeyInfo =
|
||||
CommonDataManage().currentKeyInfo;
|
||||
final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo;
|
||||
|
||||
var lockId = currentKeyInfo.lockId ?? 0;
|
||||
var remoteUnlock = currentKeyInfo.lockSetting?.remoteUnlock ?? 0;
|
||||
|
||||
final lockPeerId = StartChartManage().lockPeerId;
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity =
|
||||
await Storage.getLockMainListData();
|
||||
final LockListInfoGroupEntity? lockListInfoGroupEntity = await Storage.getLockMainListData();
|
||||
if (lockListInfoGroupEntity != null) {
|
||||
lockListInfoGroupEntity!.groupList?.forEach((element) {
|
||||
final lockList = element.lockList;
|
||||
@ -728,8 +691,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
});
|
||||
}
|
||||
if (remoteUnlock == 1) {
|
||||
final LoginEntity entity = await ApiRepository.to
|
||||
.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
|
||||
final LoginEntity entity = await ApiRepository.to.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
showToast('已开锁'.tr);
|
||||
StartChartManage().lockListPeerId = [];
|
||||
@ -756,8 +718,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
state.startRecordingAudioTime.value = DateTime.now();
|
||||
|
||||
// 增加录音帧监听器和错误监听器
|
||||
state.voiceProcessor
|
||||
?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
|
||||
state.voiceProcessor?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
|
||||
state.voiceProcessor?.addErrorListener(_onError);
|
||||
} else {
|
||||
// state.errorMessage.value = 'Recording permission not granted';
|
||||
@ -777,8 +738,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
state.endRecordingAudioTime.value = DateTime.now();
|
||||
|
||||
// 计算录音的持续时间
|
||||
final Duration duration = state.endRecordingAudioTime.value
|
||||
.difference(state.startRecordingAudioTime.value);
|
||||
final Duration duration = state.endRecordingAudioTime.value.difference(state.startRecordingAudioTime.value);
|
||||
|
||||
state.recordingAudioTime.value = duration.inSeconds;
|
||||
} on PlatformException catch (ex) {
|
||||
@ -848,10 +808,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
_bufferedAudioFrames.addAll(encodedData);
|
||||
|
||||
// 启动定时发送器(仅启动一次)
|
||||
if (_startProcessingAudioTimer == null &&
|
||||
_bufferedAudioFrames.length > chunkSize) {
|
||||
_startProcessingAudioTimer =
|
||||
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
||||
if (_startProcessingAudioTimer == null && _bufferedAudioFrames.length > chunkSize) {
|
||||
_startProcessingAudioTimer = Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -887,8 +845,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
/// 修改发送预期数据
|
||||
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: talkExpectReq);
|
||||
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq);
|
||||
|
||||
// 不立即loading,继续解码旧流帧,等待frameSeq回绕检测
|
||||
// 仅重置frameSeq回绕检测标志
|
||||
|
||||
@ -29,15 +29,12 @@ class TalkViewNativeDecodePage extends StatefulWidget {
|
||||
const TalkViewNativeDecodePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TalkViewNativeDecodePage> createState() =>
|
||||
_TalkViewNativeDecodePageState();
|
||||
State<TalkViewNativeDecodePage> createState() => _TalkViewNativeDecodePageState();
|
||||
}
|
||||
|
||||
class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
with TickerProviderStateMixin {
|
||||
class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage> with TickerProviderStateMixin {
|
||||
final TalkViewNativeDecodeLogic logic = Get.put(TalkViewNativeDecodeLogic());
|
||||
final TalkViewNativeDecodeState state =
|
||||
Get.find<TalkViewNativeDecodeLogic>().state;
|
||||
final TalkViewNativeDecodeState state = Get.find<TalkViewNativeDecodeLogic>().state;
|
||||
final startChartManage = StartChartManage();
|
||||
|
||||
@override
|
||||
@ -69,56 +66,39 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
// 返回 false 表示禁止退出
|
||||
return false;
|
||||
},
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
// 悬浮帧率统计信息条
|
||||
Obx(
|
||||
() {
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
final double screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
final double logicalWidth = MediaQuery.of(context).size.width;
|
||||
final double logicalHeight = MediaQuery.of(context).size.height;
|
||||
final double devicePixelRatio =
|
||||
MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
// 计算物理像素值
|
||||
final double physicalWidth = logicalWidth * devicePixelRatio;
|
||||
final double physicalHeight = logicalHeight * devicePixelRatio;
|
||||
|
||||
// 旋转后的图片尺寸
|
||||
const int rotatedImageWidth = 480; // 原始高度
|
||||
const int rotatedImageHeight = 864; // 原始宽度
|
||||
|
||||
// 计算缩放比例
|
||||
final double scaleWidth = physicalWidth / rotatedImageWidth;
|
||||
final double scaleHeight = physicalHeight / rotatedImageHeight;
|
||||
max(scaleWidth, scaleHeight); // 选择较大的缩放比例
|
||||
// 防御性处理:只要loading中或textureId为null,优先渲染loading/占位
|
||||
if (state.isLoading.isTrue || state.textureId.value == null) {
|
||||
return Image.asset(
|
||||
'images/main/monitorBg.png',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
} else {
|
||||
return Positioned.fill(
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: PopScope(
|
||||
canPop: false,
|
||||
child: RepaintBoundary(
|
||||
key: state.globalKey,
|
||||
child: SizedBox.expand(
|
||||
child: RotatedBox(
|
||||
// 解码器不支持硬件旋转,使用RotatedBox
|
||||
quarterTurns: startChartManage.rotateAngle ~/ 90,
|
||||
child: Platform.isIOS
|
||||
? Transform.scale(
|
||||
scale: 1.008, // 轻微放大,消除iOS白边
|
||||
child: state.isFullScreen.isFalse
|
||||
? AspectRatio(
|
||||
aspectRatio: StartChartManage().videoWidth / StartChartManage().videoHeight,
|
||||
child: Texture(
|
||||
textureId: state.textureId.value!,
|
||||
filterQuality: FilterQuality.medium,
|
||||
@ -131,7 +111,6 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -151,19 +130,14 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
width: 1.sw,
|
||||
child: Obx(
|
||||
() {
|
||||
final String sec = (state.oneMinuteTime.value % 60)
|
||||
.toString()
|
||||
.padLeft(2, '0');
|
||||
final String min = (state.oneMinuteTime.value ~/ 60)
|
||||
.toString()
|
||||
.padLeft(2, '0');
|
||||
final String sec = (state.oneMinuteTime.value % 60).toString().padLeft(2, '0');
|
||||
final String min = (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0');
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'$min:$sec',
|
||||
style: TextStyle(
|
||||
fontSize: 26.sp, color: Colors.white),
|
||||
style: TextStyle(fontSize: 26.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -177,9 +151,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
width: 1.sw - 30.w * 2,
|
||||
// height: 300.h,
|
||||
margin: EdgeInsets.all(30.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20.h)),
|
||||
decoration: BoxDecoration(color: Colors.black.withOpacity(0.2), borderRadius: BorderRadius.circular(20.h)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20.h),
|
||||
@ -191,9 +163,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(() => state.isLoading.isTrue
|
||||
? buildRotationTransition()
|
||||
: Container()),
|
||||
Obx(() => state.isLoading.isTrue ? buildRotationTransition() : Container()),
|
||||
Obx(() => state.isLongPressing.value
|
||||
? Positioned(
|
||||
top: 80.h,
|
||||
@ -213,8 +183,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
SizedBox(width: 10.w),
|
||||
Text(
|
||||
'正在说话...'.tr,
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp, color: Colors.white),
|
||||
style: TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -246,10 +215,8 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
image: state.isOpenVoice.value
|
||||
? const AssetImage(
|
||||
'images/main/icon_lockDetail_monitoringOpenVoice.png')
|
||||
: const AssetImage(
|
||||
'images/main/icon_lockDetail_monitoringCloseVoice.png'))),
|
||||
? const AssetImage('images/main/icon_lockDetail_monitoringOpenVoice.png')
|
||||
: const AssetImage('images/main/icon_lockDetail_monitoringCloseVoice.png'))),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 50.w),
|
||||
@ -264,11 +231,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image(
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
image: const AssetImage(
|
||||
'images/main/icon_lockDetail_monitoringScreenshot.png')),
|
||||
child: Image(width: 40.w, height: 40.w, image: const AssetImage('images/main/icon_lockDetail_monitoringScreenshot.png')),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 50.w),
|
||||
@ -293,8 +256,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
fit: BoxFit.fill,
|
||||
image: const AssetImage(
|
||||
'images/main/icon_lockDetail_monitoringScreenRecording.png'),
|
||||
image: const AssetImage('images/main/icon_lockDetail_monitoringScreenRecording.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -330,13 +292,8 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
Text(
|
||||
q,
|
||||
style: TextStyle(
|
||||
color: state.currentQuality.value == q
|
||||
? AppColors.mainColor
|
||||
: Colors.black,
|
||||
fontWeight:
|
||||
state.currentQuality.value == q
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: state.currentQuality.value == q ? AppColors.mainColor : Colors.black,
|
||||
fontWeight: state.currentQuality.value == q ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 28.sp,
|
||||
),
|
||||
),
|
||||
@ -352,8 +309,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
child: Icon(Icons.high_quality_outlined,
|
||||
color: Colors.white, size: 38.w),
|
||||
child: Icon(Icons.high_quality_outlined, color: Colors.white, size: 38.w),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
@ -377,9 +333,7 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
}
|
||||
|
||||
Widget bottomBottomBtnWidget() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
|
||||
// 接听
|
||||
Obx(
|
||||
() => bottomBtnItemWidget(
|
||||
@ -399,17 +353,14 @@ class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
|
||||
state.isLongPressing.value = false;
|
||||
},
|
||||
onClick: () async {
|
||||
if (state.talkStatus.value ==
|
||||
TalkStatus.passiveCallWaitingAnswer) {
|
||||
if (state.talkStatus.value == TalkStatus.passiveCallWaitingAnswer) {
|
||||
// 接听
|
||||
logic.initiateAnswerCommand();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomBtnItemWidget(
|
||||
'images/main/icon_lockDetail_hangUp.png', '挂断'.tr, Colors.red,
|
||||
onClick: () {
|
||||
bottomBtnItemWidget('images/main/icon_lockDetail_hangUp.png', '挂断'.tr, Colors.red, onClick: () {
|
||||
// 挂断
|
||||
logic.udpHangUpAction();
|
||||
}),
|
||||
|
||||
@ -0,0 +1,518 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:star_lock/appRouters.dart';
|
||||
import 'package:star_lock/flavors.dart';
|
||||
import 'package:star_lock/talk/call/callTalk.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/impl/udp_talk_data_handler.dart';
|
||||
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
|
||||
import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_logic.dart';
|
||||
import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_state.dart';
|
||||
import 'package:star_lock/talk/starChart/views/talkView/talk_view_logic.dart';
|
||||
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
|
||||
import 'package:video_decode_plugin/video_decode_plugin.dart';
|
||||
|
||||
import '../../../../app_settings/app_colors.dart';
|
||||
import '../../../../tools/showTFView.dart';
|
||||
|
||||
class TalkViewNativeDecodePageDebug extends StatefulWidget {
|
||||
const TalkViewNativeDecodePageDebug({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TalkViewNativeDecodePageDebug> createState() => _TalkViewNativeDecodePageDebugState();
|
||||
}
|
||||
|
||||
class _TalkViewNativeDecodePageDebugState extends State<TalkViewNativeDecodePageDebug> with TickerProviderStateMixin {
|
||||
final TalkViewNativeDecodeLogic logic = Get.put(TalkViewNativeDecodeLogic());
|
||||
final TalkViewNativeDecodeState state = Get.find<TalkViewNativeDecodeLogic>().state;
|
||||
final startChartManage = StartChartManage();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
state.animationController = AnimationController(
|
||||
vsync: this, // 确保使用的TickerProvider是当前Widget
|
||||
duration: const Duration(seconds: 1),
|
||||
);
|
||||
|
||||
state.animationController.repeat();
|
||||
//动画开始、结束、向前移动或向后移动时会调用StatusListener
|
||||
state.animationController.addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
state.animationController.reset();
|
||||
state.animationController.forward();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
state.animationController.reset();
|
||||
state.animationController.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
// 返回 false 表示禁止退出
|
||||
return false;
|
||||
},
|
||||
child: Container(
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
// 悬浮帧率统计信息条
|
||||
Obx(
|
||||
() {
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
final double screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
// 防御性处理:只要loading中或textureId为null,优先渲染loading/占位
|
||||
if (state.isLoading.isTrue || state.textureId.value == null) {
|
||||
return Image.asset(
|
||||
'images/main/monitorBg.png',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
} else {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: RepaintBoundary(
|
||||
key: state.globalKey,
|
||||
child: RotatedBox(
|
||||
// 解码器不支持硬件旋转,使用RotatedBox
|
||||
quarterTurns: startChartManage.rotateAngle ~/ 90,
|
||||
child: Platform.isIOS
|
||||
? Transform.scale(
|
||||
scale: 1.008, // 轻微放大,消除iOS白边
|
||||
child: Texture(
|
||||
textureId: state.textureId.value!,
|
||||
filterQuality: FilterQuality.medium,
|
||||
),
|
||||
)
|
||||
: state.isFullScreen.isFalse
|
||||
? AspectRatio(
|
||||
aspectRatio: StartChartManage().videoWidth / StartChartManage().videoHeight,
|
||||
child: Texture(
|
||||
textureId: state.textureId.value!,
|
||||
filterQuality: FilterQuality.medium,
|
||||
),
|
||||
)
|
||||
: Texture(
|
||||
textureId: state.textureId.value!,
|
||||
filterQuality: FilterQuality.medium,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
state.isFullScreen.value = !state.isFullScreen.value;
|
||||
},
|
||||
child: Obx(
|
||||
() => Text(state.isFullScreen.isTrue ? '退出全屏' : '全屏'),
|
||||
),
|
||||
),
|
||||
Obx(() => state.isLoading.isTrue
|
||||
? Positioned(
|
||||
bottom: 310.h,
|
||||
child: Text(
|
||||
'正在创建安全连接...'.tr,
|
||||
style: TextStyle(color: Colors.black, fontSize: 26.sp),
|
||||
))
|
||||
: Container()),
|
||||
Obx(() => state.isLoading.isFalse && state.oneMinuteTime.value > 0
|
||||
? Positioned(
|
||||
top: ScreenUtil().statusBarHeight + 75.h,
|
||||
width: 1.sw,
|
||||
child: Obx(
|
||||
() {
|
||||
final String sec = (state.oneMinuteTime.value % 60).toString().padLeft(2, '0');
|
||||
final String min = (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0');
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'$min:$sec',
|
||||
style: TextStyle(fontSize: 26.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Container()),
|
||||
Positioned(
|
||||
bottom: 10.w,
|
||||
child: Container(
|
||||
width: 1.sw - 30.w * 2,
|
||||
// height: 300.h,
|
||||
margin: EdgeInsets.all(30.w),
|
||||
decoration: BoxDecoration(color: Colors.black.withOpacity(0.2), borderRadius: BorderRadius.circular(20.h)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20.h),
|
||||
bottomTopBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
bottomBottomBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(() => state.isLoading.isTrue ? buildRotationTransition() : Container()),
|
||||
Obx(() => state.isLongPressing.value
|
||||
? Positioned(
|
||||
top: 80.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(10.w),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(Icons.mic, color: Colors.white, size: 24.w),
|
||||
SizedBox(width: 10.w),
|
||||
Text(
|
||||
'正在说话...'.tr,
|
||||
style: TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTopBtnWidget() {
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
// 打开关闭声音
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
// 打开关闭声音
|
||||
logic.updateTalkExpect();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Obx(() => Image(
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
image: state.isOpenVoice.value
|
||||
? const AssetImage('images/main/icon_lockDetail_monitoringOpenVoice.png')
|
||||
: const AssetImage('images/main/icon_lockDetail_monitoringCloseVoice.png'))),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 50.w),
|
||||
// 截图
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
await logic.captureAndSavePng();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image(width: 40.w, height: 40.w, image: const AssetImage('images/main/icon_lockDetail_monitoringScreenshot.png')),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 50.w),
|
||||
// 录制
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
logic.showToast('功能暂未开放'.tr);
|
||||
// if (
|
||||
// state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
// if (state.isRecordingScreen.value) {
|
||||
// await logic.stopRecording();
|
||||
// } else {
|
||||
// await logic.startRecording();
|
||||
// }
|
||||
// }
|
||||
},
|
||||
child: Container(
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image(
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
fit: BoxFit.fill,
|
||||
image: const AssetImage('images/main/icon_lockDetail_monitoringScreenRecording.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 50.w),
|
||||
// 清晰度切换按钮
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// 弹出底部弹出层,选择清晰度
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20.w)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
final List<String> qualities = ['高清', '标清'];
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: qualities.map((q) {
|
||||
return Obx(() => InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
logic.onQualityChanged(q);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 18.w),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
q,
|
||||
style: TextStyle(
|
||||
color: state.currentQuality.value == q ? AppColors.mainColor : Colors.black,
|
||||
fontWeight: state.currentQuality.value == q ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 28.sp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
child: Icon(Icons.high_quality_outlined, color: Colors.white, size: 38.w),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: state.currentLanguage == 'zh_CN' && Platform.isAndroid,
|
||||
child: SizedBox(width: 38.w),
|
||||
),
|
||||
Visibility(
|
||||
visible: state.currentLanguage == 'zh_CN' && Platform.isAndroid,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.notification_add_sharp,
|
||||
size: 32.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
Get.toNamed(Routers.permissionGuidancePage);
|
||||
},
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
Widget bottomBottomBtnWidget() {
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
|
||||
// 接听
|
||||
Obx(
|
||||
() => bottomBtnItemWidget(
|
||||
getAnswerBtnImg(),
|
||||
getAnswerBtnName(),
|
||||
Colors.white,
|
||||
longPress: () async {
|
||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
// 启动录音
|
||||
logic.startProcessingAudio();
|
||||
state.isLongPressing.value = true;
|
||||
}
|
||||
},
|
||||
longPressUp: () async {
|
||||
// 停止录音
|
||||
logic.stopProcessingAudio();
|
||||
state.isLongPressing.value = false;
|
||||
},
|
||||
onClick: () async {
|
||||
if (state.talkStatus.value == TalkStatus.passiveCallWaitingAnswer) {
|
||||
// 接听
|
||||
logic.initiateAnswerCommand();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomBtnItemWidget('images/main/icon_lockDetail_hangUp.png', '挂断'.tr, Colors.red, onClick: () {
|
||||
// 挂断
|
||||
logic.udpHangUpAction();
|
||||
}),
|
||||
bottomBtnItemWidget(
|
||||
'images/main/icon_lockDetail_monitoringUnlock.png',
|
||||
'开锁'.tr,
|
||||
AppColors.mainColor,
|
||||
onClick: () {
|
||||
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully &&
|
||||
// state.listData.value.length > 0) {
|
||||
// logic.udpOpenDoorAction();
|
||||
// }
|
||||
// if (UDPManage().remoteUnlock == 1) {
|
||||
// logic.udpOpenDoorAction();
|
||||
// showDeletPasswordAlertDialog(context);
|
||||
// } else {
|
||||
// logic.showToast('请在锁设置中开启远程开锁'.tr);
|
||||
// }
|
||||
logic.remoteOpenLock();
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
String getAnswerBtnImg() {
|
||||
switch (state.talkStatus.value) {
|
||||
case TalkStatus.passiveCallWaitingAnswer:
|
||||
return 'images/main/icon_lockDetail_monitoringAnswerCalls.png';
|
||||
case TalkStatus.answeredSuccessfully:
|
||||
case TalkStatus.proactivelyCallWaitingAnswer:
|
||||
return 'images/main/icon_lockDetail_monitoringUnTalkback.png';
|
||||
default:
|
||||
return 'images/main/icon_lockDetail_monitoringAnswerCalls.png';
|
||||
}
|
||||
}
|
||||
|
||||
String getAnswerBtnName() {
|
||||
switch (state.talkStatus.value) {
|
||||
case TalkStatus.passiveCallWaitingAnswer:
|
||||
return '接听'.tr;
|
||||
case TalkStatus.proactivelyCallWaitingAnswer:
|
||||
case TalkStatus.answeredSuccessfully:
|
||||
return '长按说话'.tr;
|
||||
default:
|
||||
return '接听'.tr;
|
||||
}
|
||||
}
|
||||
|
||||
Widget bottomBtnItemWidget(
|
||||
String iconUrl,
|
||||
String name,
|
||||
Color backgroundColor, {
|
||||
required Function() onClick,
|
||||
Function()? longPress,
|
||||
Function()? longPressUp,
|
||||
}) {
|
||||
double wh = 80.w;
|
||||
return GestureDetector(
|
||||
onTap: onClick,
|
||||
onLongPress: longPress,
|
||||
onLongPressUp: longPressUp,
|
||||
child: SizedBox(
|
||||
height: 160.w,
|
||||
width: 140.w,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: wh,
|
||||
height: wh,
|
||||
constraints: BoxConstraints(
|
||||
minWidth: wh,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2),
|
||||
),
|
||||
padding: EdgeInsets.all(20.w),
|
||||
child: Image.asset(iconUrl, fit: BoxFit.fitWidth),
|
||||
),
|
||||
SizedBox(height: 20.w),
|
||||
Text(
|
||||
name,
|
||||
style: TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||
textAlign: TextAlign.center, // 当文本超出指定行数时,使用省略号表示
|
||||
maxLines: 2, // 设置最大行数为1
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 根据丢包率返回对应的颜色
|
||||
Color _getPacketLossColor(double lossRate) {
|
||||
if (lossRate < 1.0) {
|
||||
return Colors.green; // 丢包率低于1%显示绿色
|
||||
} else if (lossRate < 5.0) {
|
||||
return Colors.yellow; // 丢包率1%-5%显示黄色
|
||||
} else if (lossRate < 10.0) {
|
||||
return Colors.orange; // 丢包率5%-10%显示橙色
|
||||
} else {
|
||||
return Colors.red; // 丢包率高于10%显示红色
|
||||
}
|
||||
}
|
||||
|
||||
//旋转动画
|
||||
Widget buildRotationTransition() {
|
||||
return Positioned(
|
||||
left: ScreenUtil().screenWidth / 2 - 220.w / 2,
|
||||
top: ScreenUtil().screenHeight / 2 - 220.w / 2 - 150.h,
|
||||
child: GestureDetector(
|
||||
child: RotationTransition(
|
||||
//设置动画的旋转中心
|
||||
alignment: Alignment.center,
|
||||
//动画控制器
|
||||
turns: state.animationController,
|
||||
//将要执行动画的子view
|
||||
child: AnimatedOpacity(
|
||||
opacity: 0.5,
|
||||
duration: const Duration(seconds: 2),
|
||||
child: Image.asset(
|
||||
'images/main/realTime_connecting.png',
|
||||
width: 220.w,
|
||||
height: 220.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
state.animationController.forward();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
state.animationController.dispose();
|
||||
CallTalk().finishAVData();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@ -37,8 +37,7 @@ class TalkViewNativeDecodeState {
|
||||
Future<String?> userMobileIP = NetworkInfo().getWifiIP();
|
||||
Future<String?> userUid = Storage.getUid();
|
||||
|
||||
RxInt udpStatus =
|
||||
0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话
|
||||
RxInt udpStatus = 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话
|
||||
TextEditingController passwordTF = TextEditingController();
|
||||
|
||||
RxList<int> listAudioData = <int>[].obs; //得到的音频流字节数据
|
||||
@ -63,13 +62,12 @@ class TalkViewNativeDecodeState {
|
||||
RxBool isPlaying = false.obs; // 是否开始播放
|
||||
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //星图对讲状态
|
||||
// 获取 startChartTalkStatus 的唯一实例
|
||||
final StartChartTalkStatus startChartTalkStatus =
|
||||
StartChartTalkStatus.instance;
|
||||
final StartChartTalkStatus startChartTalkStatus = StartChartTalkStatus.instance;
|
||||
|
||||
// 通话数据流的单例流数据处理类
|
||||
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
|
||||
|
||||
RxBool isOpenVoice = true.obs; // 是否打开声音
|
||||
RxBool isOpenVoice = false.obs; // 是否打开声音
|
||||
RxBool isRecordingScreen = false.obs; // 是否录屏中
|
||||
RxBool isRecordingAudio = false.obs; // 是否录音中
|
||||
Rx<DateTime> startRecordingAudioTime = DateTime.now().obs; // 开始录音时间
|
||||
@ -81,6 +79,7 @@ class TalkViewNativeDecodeState {
|
||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||
// 视频解码器纹理ID
|
||||
Rx<int?> textureId = Rx<int?>(null);
|
||||
|
||||
// FPS监测相关变量
|
||||
|
||||
RxInt lastFpsUpdateTime = 0.obs; // 上次FPS更新时间
|
||||
@ -110,8 +109,8 @@ class TalkViewNativeDecodeState {
|
||||
|
||||
// H264帧缓冲区相关
|
||||
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
||||
final int maxFrameBufferSize = 50; // 最大缓冲区大小
|
||||
final int targetFps = 60; // 目标解码帧率,只是为了快速填充native的缓冲区
|
||||
final int maxFrameBufferSize = 25; // 最大缓冲区大小
|
||||
final int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
||||
Timer? frameProcessTimer; // 帧处理定时器
|
||||
bool isProcessingFrame = false; // 是否正在处理帧
|
||||
int lastProcessedTimestamp = 0; // 上次处理帧的时间戳
|
||||
@ -122,6 +121,8 @@ class TalkViewNativeDecodeState {
|
||||
// 当前清晰度选项,初始为'高清'
|
||||
RxString currentQuality = '高清'.obs; // 可选:高清、标清、流畅
|
||||
|
||||
RxString currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
RxString currentLanguage = CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
|
||||
// 是否拉伸至全屏
|
||||
RxBool isFullScreen = false.obs;
|
||||
}
|
||||
|
||||
@ -322,6 +322,7 @@ flutter:
|
||||
assets:
|
||||
- images/
|
||||
- images/tabbar/
|
||||
- images/other/
|
||||
- images/guide/
|
||||
- images/main/
|
||||
- images/main/addFingerprint/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user