Merge branch 'develop_sky' into 'master_sky'

fix:测试ci

See merge request StarlockTeam/app-starlock!38
This commit is contained in:
李仪 2025-05-09 08:39:14 +00:00
commit c95b0b9721
59 changed files with 3251 additions and 738 deletions

View File

@ -18,6 +18,7 @@ variables:
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
- if: $CI_COMMIT_BRANCH == "canary_release"
- if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$/
@ -29,6 +30,7 @@ variables:
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
.generate_tag_rule:
@ -37,6 +39,7 @@ variables:
- flutter
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_BRANCH == "master_sky"
.generate_next_version_rule:
tags:
@ -45,6 +48,7 @@ variables:
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH == "canary_release"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/

View File

@ -1,5 +1,5 @@
# 星锁APP
测试ci
星云项目组旗下的智能锁应用,其中锁相关数据接入星云平台,业务数据接入星锁自有后台。
基于Flutter技术架构支持Android和iOS平台。

View File

@ -28,5 +28,9 @@ elif [[ "${ENV_BUILD_BRANCH}" == "release" ]] || [[ "${ENV_BUILD_BRANCH}" == "fe
echo "===build pre===${NEXT_VERSION}"
bundle exec fastlane beta flavor:xhj env:pre --verbose
bundle exec fastlane beta flavor:sky env:pre --verbose
elif [[ "${ENV_BUILD_BRANCH}" == "release_sky" ]]; then
echo "===build release_sky===${NEXT_VERSION}"
bundle exec fastlane release_apk flavor:sky --verbose
bundle exec fastlane release_bundle flavor:sky --verbose
fi
exit 0

View File

@ -29,8 +29,8 @@
#player {
object-fit: cover;
height: 56vh;
transform: rotate(-90deg);
width: 100vw;
height: 100vh;
}
</style>

View File

@ -60,6 +60,7 @@ import 'package:star_lock/mine/mineSet/transferSmartLock/transferSmartLockList/t
import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_page.dart';
import 'package:star_lock/mine/valueAddedServices/advancedFunctionRecord/advancedFunctionRecord_page.dart';
import 'package:star_lock/mine/valueAddedServices/valueAddedServicesRecord/value_added_services_record_page.dart';
import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_page.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_page.dart';
import 'package:star_lock/talk/starChart/webView/h264_web_view.dart';
@ -1184,6 +1185,7 @@ abstract class AppRouters {
page: () => const DoubleLockLinkPage()),
GetPage<dynamic>(
name: Routers.starChartTalkView, page: () => const TalkViewPage()),
GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()),
GetPage<dynamic>(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), //
// GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面
];
}

View File

@ -5,6 +5,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart';
@ -262,7 +263,7 @@ class BlueManage {
for (final ScanResult scanResult in results) {
if (scanResult.advertisementData.serviceUuids.isNotEmpty) {
// AppLog.log(
// '扫描到的设备:${scanResult.advertisementData.serviceUuids[0].toString()}');
// '扫描到的设备:${scanResult.advertisementData.serviceUuids[0].toString()}====${scanResult.advertisementData.advName}');
} else {
continue;
}
@ -315,20 +316,30 @@ class BlueManage {
}
/// uuid
bool _isMatch(List<String> serviceUuids,
{DeviceType deviceType = DeviceType.blue}) {
//
List<String> deviceTypeList = getDeviceType(deviceType);
// serviceUuids deviceTypeList
if (serviceUuids != null && serviceUuids.isNotEmpty) {
return serviceUuids.any((uuid) {
return deviceTypeList
.any((type) => uuid.toLowerCase().contains(type.toLowerCase()));
});
bool _isMatch(List<String> serviceUuids, {DeviceType deviceType = DeviceType.blue}) {
final List<String> prefixes = getDeviceType(deviceType).map((e) => e.toLowerCase()).toList();
for (String uuid in serviceUuids) {
final String cleanUuid = uuid.replaceAll('-', '').toLowerCase();
if (cleanUuid.length == 8) {
// 8
for (final prefix in prefixes) {
if (cleanUuid.startsWith(prefix)) {
return true;
}
}
} else if (cleanUuid.length == 32) {
// 128834
final String first8 = cleanUuid.substring(0, 8);
if (first8.length >= 4) {
final String thirdAndFourth = first8.substring(2, 4); // 23
for (final prefix in prefixes) {
if (thirdAndFourth == prefix) {
return true;
}
}
}
}
}
// serviceUuids false
return false;
}
@ -551,7 +562,10 @@ class BlueManage {
});
} else {
connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear();
BuglyTool.uploadException(
@ -582,7 +596,9 @@ class BlueManage {
});
} else {
connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear();
BuglyTool.uploadException(
@ -775,7 +791,7 @@ class BlueManage {
}
}
//
///
Future<void> writeCharacteristicWithResponse(List<int> value) async {
final List<BluetoothService> services =
await bluetoothConnectDevice!.discoverServices();
@ -785,30 +801,70 @@ class BlueManage {
in service.characteristics) {
if (characteristic.characteristicUuid == _characteristicIdWrite) {
try {
//
int retryCount = 0;
const int maxRetries = 3;
const int retryDelayMs = 500;
final List<int> valueList = value;
final List subData = splitList(valueList, _mtuSize!);
// AppLog.log('writeCharacteristicWithResponse _mtuSize:$_mtuSize 得到的分割数据:$subData');
for (int i = 0; i < subData.length; i++) {
if (characteristic.properties.writeWithoutResponse) {
// 使WRITE_NO_RESPONSE属性写入值
await characteristic.write(subData[i], withoutResponse: true);
} else if (characteristic.properties.write) {
// 使WRITE属性写入值
await characteristic.write(subData[i]);
} else {
//
//
bool packetSent = false;
retryCount = 0;
while (!packetSent && retryCount < maxRetries) {
try {
if (characteristic.properties.writeWithoutResponse) {
await characteristic.write(subData[i],
withoutResponse: true);
} else if (characteristic.properties.write) {
await characteristic.write(subData[i]);
} else {
//
throw Exception(
'This characteristic does not support writing.');
}
//
packetSent = true;
} catch (e) {
if (e.toString().contains('UNKNOWN_GATT_ERROR (133)') &&
retryCount < maxRetries - 1) {
// GATT错误133
retryCount++;
AppLog.log(
'蓝牙写入失败(GATT 133),数据包 ${i + 1}/${subData.length} 正在重试 $retryCount/$maxRetries...');
await Future.delayed(
Duration(milliseconds: retryDelayMs));
continue;
} else {
//
AppLog.log('APP写入失败: $e');
throw e;
}
}
}
if (!packetSent) {
throw Exception(
'This characteristic does not support writing.');
'蓝牙写入失败,数据包 ${i + 1}/${subData.length} 已达到最大重试次数');
}
}
return; //
} on Exception catch (e, s) {
AppLog.log('APP写入失败: $e $s');
AppLog.log('APP写入失败: $e $s');
rethrow;
}
}
}
}
}
//
throw Exception('未找到适合写入的蓝牙特性');
}
//

View File

@ -9,7 +9,7 @@ List<String> getDeviceType(DeviceType deviceType) {
List<String> t = ['758824'];
switch (deviceType) {
case DeviceType.blue:
t = ['758824', '75'];
t = ['758824', '75', '768824', '76'];
break;
case DeviceType.gateway:
t = ['758825'];

View File

@ -61,16 +61,16 @@ class CommandReciverManager {
final int dataSize = data.length;
// CRC校验
if (dataSize >= 2) {
final int calculatedCrc =
_calculateCRC16(data.sublist(0, dataSize - 2), dataSize - 2);
final int receivedCrc = (data[dataSize - 2] << 8) | data[dataSize - 1];
if (calculatedCrc != receivedCrc) {
AppLog.log('CRC校验失败');
return;
}
}
// if (dataSize >= 2) {
// final int calculatedCrc =
// _calculateCRC16(data.sublist(0, dataSize - 2), dataSize - 2);
// final int receivedCrc = (data[dataSize - 2] << 8) | data[dataSize - 1];
//
// if (calculatedCrc != receivedCrc) {
// throw Exception('CRC校验失败');
// return;
// }
// }
// 13
if (dataSize < 13) {
return;

View File

@ -76,7 +76,6 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
Container(
width: 340.w,
height: 60.h,
// color: Colors.red,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30.h)),
border:
@ -119,28 +118,22 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
child: Obx(
() => Container(
height: 60.h,
// color: Colors.red,
decoration: state.isIphoneType.value
? null
: BoxDecoration(
decoration: !state.isIphoneType.value
? BoxDecoration(
color: AppColors.mainColor,
borderRadius: BorderRadius.all(
Radius.circular(
30.h,
),
),
borderRadius:
BorderRadius.all(Radius.circular(30.h)),
border: Border.all(
width: 1.0,
color: AppColors.greyLineColor,
),
),
width: 1.0,
color: AppColors.greyLineColor))
: null,
child: Center(
child: Text(
'邮箱'.tr,
style: TextStyle(
color: state.isIphoneType.value
? Colors.black
: Colors.white,
color: !state.isIphoneType.value
? Colors.white
: Colors.black,
),
),
),

View File

@ -3,8 +3,11 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
class StarLockRegisterState {
StarLockRegisterState() {
// tab
final Locale? systemLocale = Get.deviceLocale;
isIphoneType.value = systemLocale?.languageCode == 'zh';
resetResend();
}
final TextEditingController phoneOrEmailController = TextEditingController();
@ -22,16 +25,21 @@ class StarLockRegisterState {
RxString verificationCode = ''.obs;
RxString xWidth = ''.obs; //
RxBool isIphoneType = true.obs;
RxBool canSub = false.obs;//
RxBool canSub = false.obs; //
RxBool agree = false.obs;
RxBool canSendCode = false.obs;//
RxBool canSendCode = false.obs; //
// bool get isEmail => RegexUtil.isEmail(phoneOrEmailStr.value);
// bool get isIphone => RegexUtil.isMobileSimple(phoneOrEmailStr.value);
bool get pwdIsOK => pwd.value.isNotEmpty && surePwd.value.isNotEmpty && pwd.value.length >= 8 && surePwd.value.length >= 8;
bool get codeIsOK => verificationCode.value.isNotEmpty && verificationCode.value.length >= 6 ;
bool get pwdIsOK =>
pwd.value.isNotEmpty &&
surePwd.value.isNotEmpty &&
pwd.value.length >= 8 &&
surePwd.value.length >= 8;
bool get codeIsOK =>
verificationCode.value.isNotEmpty && verificationCode.value.length >= 6;
RxBool canResend = false.obs;//
RxBool canResend = false.obs; //
RxString btnText = ''.obs;
int totalSeconds = 120;
int currentSecond = 120;
@ -39,9 +47,8 @@ class StarLockRegisterState {
void resetResend() {
canResend.value = totalSeconds == currentSecond;
btnText.value = !canResend.value
? '$currentSecond s'
: btnText.value = '获取验证码'.tr;
btnText.value =
!canResend.value ? '$currentSecond s' : btnText.value = '获取验证码'.tr;
}
void onClose() {

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -74,6 +73,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
height: 80.h,
child: DefaultTabController(
length: 2,
initialIndex: state.isIphoneType.value ? 0 : 1,
child: TabBar(
onTap: (int index) {
state.isIphoneType.value = index == 0;
@ -84,10 +84,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
}),
dividerHeight: 0,
indicatorSize: TabBarIndicatorSize.tab,
tabs: <Widget>[
Text('手机'.tr),
Text('邮箱'.tr)
],
tabs: <Widget>[Text('手机'.tr), Text('邮箱'.tr)],
indicatorColor: AppColors.mainColor,
labelStyle:
TextStyle(color: AppColors.mainColor, fontSize: 26.sp),
@ -144,7 +141,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
logic.checkNext(state.phoneOrEmailController);
},
leftWidget: SizedBox(),
label:state.isIphoneType.value ? '请输入手机号'.tr : '请输入邮箱'.tr,
label: state.isIphoneType.value ? '请输入手机号'.tr : '请输入邮箱'.tr,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
LengthLimitingTextInputFormatter(30),
@ -163,7 +160,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
Text(
'密码必须是8-20位至少包括数字/字母/符号中的2种'.tr,
style:
TextStyle(color: AppColors.placeholderTextColor, fontSize: 20.sp),
TextStyle(color: AppColors.placeholderTextColor, fontSize: 20.sp),
),
LoginInput(
controller: state.sureController,
@ -235,32 +232,31 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
Widget _buildBottomAgreement() {
return Padding(
padding: EdgeInsets.only(bottom:20.w),
padding: EdgeInsets.only(bottom: 20.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Obx(() => GestureDetector(
onTap: () {
state.agree.value = !state.agree.value;
logic.changeAgreeState();
},
child: Container(
width: 40.w,
height: 40.w,
// color: Colors.red,
padding: EdgeInsets.only(
left: 5.w,
right: 10.w,
),
child: Image.asset(
state.agree.value
? 'images/icon_round_select.png'
: 'images/icon_round_unSelect.png',
width: 20.w,
height: 20.w,
),
)
)),
onTap: () {
state.agree.value = !state.agree.value;
logic.changeAgreeState();
},
child: Container(
width: 40.w,
height: 40.w,
// color: Colors.red,
padding: EdgeInsets.only(
left: 5.w,
right: 10.w,
),
child: Image.asset(
state.agree.value
? 'images/icon_round_select.png'
: 'images/icon_round_unSelect.png',
width: 20.w,
height: 20.w,
),
))),
SizedBox(
width: 10.w,
),
@ -268,14 +264,12 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
child: RichText(
text: TextSpan(
text: '我已阅读并同意'.tr,
style:
TextStyle(color: const Color(0xff333333), fontSize: 20.sp),
style: TextStyle(color: const Color(0xff333333), fontSize: 20.sp),
children: <InlineSpan>[
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
child: Text(
'${'用户协议'.tr}',
child: Text('${'用户协议'.tr}',
style: TextStyle(
color: AppColors.mainColor, fontSize: 20.sp)),
onTap: () {
@ -289,8 +283,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterXHJPage> {
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
child: Text(
'${'隐私政策'.tr}',
child: Text('${'隐私政策'.tr}',
style: TextStyle(
color: AppColors.mainColor, fontSize: 20.sp)),
onTap: () {

View File

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/main/lockDetail/card/cardDetail/cardDetail_state.dart';
import '../../../../appRouters.dart';
@ -179,13 +180,22 @@ class _CardDetailPageState extends State<CardDetailPage> with RouteAware {
isHaveRightWidget: true,
rightWidget: SizedBox(
width: 60.w, height: 50.h, child: _isStressFingerprint()))),
Obx(() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget:
SizedBox(width: 60.w, height: 50.h, child: _isAdmin()))),
Visibility(
visible: !F.isSKY,
child: Obx(
() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget: SizedBox(
width: 60.w,
height: 50.h,
child: _isAdmin(),
),
),
),
),
Container(height: 10.h),
CommonItem(
leftTitel: '操作记录'.tr,

View File

@ -249,8 +249,9 @@ class CardListLogic extends BaseGetXController {
_initReplySubscription();
// _initRefreshAction();
await getICCardListData(isRefresh: true);
}
await getICCardListData(isRefresh: true);
}
@override

View File

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/main/lockDetail/face/faceDetail/faceDetail_logic.dart';
import 'package:star_lock/main/lockDetail/face/faceDetail/faceDetail_state.dart';
@ -174,13 +175,22 @@ class _FaceDetailPageState extends State<FaceDetailPage> with RouteAware {
// isHaveRightWidget: true,
// isHaveLine: true,
// rightWidget: SizedBox(width: 60.w, height: 50.h, child: _isStressFace()))),
Obx(() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget:
SizedBox(width: 60.w, height: 50.h, child: _isAdmin()))),
Visibility(
visible: !F.isSKY,
child: Obx(
() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget: SizedBox(
width: 60.w,
height: 50.h,
child: _isAdmin(),
),
),
),
),
Container(height: 10.h),
CommonItem(
leftTitel: '操作记录'.tr,

View File

@ -443,8 +443,9 @@ class FaceListLogic extends BaseGetXController {
// senderCheckingCardStatus();
// senderCheckingUserInfoCount();
await getFaceListData(isRefresh: true);
}
getFaceListData(isRefresh: true);
}
@override

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
import 'package:star_lock/apm/apm_helper.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/login/login/entity/LoginEntity.dart';
import 'package:star_lock/main/lockDetail/fingerprint/addFingerprint/addFingerprint_entity.dart';
import 'package:star_lock/tools/dateTool.dart';
@ -141,7 +142,7 @@ class AddFingerprintLogic extends BaseGetXController {
break;
case 0xFE:
case 12:
//
//
state.ifAddState.value = false;
showToast('管理员已满'.tr, something: () {
Get.back();
@ -250,6 +251,12 @@ class AddFingerprintLogic extends BaseGetXController {
final List<String>? token = await Storage.getStringList(saveBlueToken);
final List<int> getTokenList = changeStringListToIntList(token!);
String startTime = DateTool().dateToHNString(state.effectiveDateTime.value);
String endTime = DateTool().dateToHNString(state.failureDateTime.value);
if (F.isSKY) {
startTime = '255:00';
endTime = '255:00';
}
final String command = SenderAddFingerprintWithTimeCycleCoercionCommand(
keyID: '1',
@ -267,8 +274,8 @@ class AddFingerprintLogic extends BaseGetXController {
//
startDate: int.parse(state.startDate.value) ~/ 1000,
endDate: int.parse(state.endDate.value) ~/ 1000,
startTime: DateTool().dateToHNString(state.effectiveDateTime.value),
endTime: DateTool().dateToHNString(state.failureDateTime.value),
startTime: startTime,
endTime: endTime,
needAuthor: 1,
signKey: signKeyDataList,
privateKey: getPrivateKeyList,
@ -324,8 +331,8 @@ class AddFingerprintLogic extends BaseGetXController {
//
startDate: int.parse(state.startDate.value) ~/ 1000,
endDate: int.parse(state.endDate.value) ~/ 1000,
startTime: DateTool().dateToHNString(state.effectiveDateTime.value),
endTime: DateTool().dateToHNString(state.failureDateTime.value),
startTime: startTime,
endTime: endTime,
needAuthor: 1,
signKey: signKeyDataList,
privateKey: getPrivateKeyList,

View File

@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/main/lockDetail/fingerprint/fingerprintDetail/fingerprintDetail_state.dart';
import '../../../../appRouters.dart';
@ -178,13 +179,22 @@ class _FingerprintDetailPageState extends State<FingerprintDetailPage>
isHaveLine: true,
rightWidget: SizedBox(
width: 60.w, height: 50.h, child: _isStressFingerprint()))),
Obx(() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget:
SizedBox(width: 60.w, height: 50.h, child: _isAdmin()))),
Visibility(
visible: !F.isSKY,
child: Obx(
() => CommonItem(
leftTitel: '是否为管理员'.tr,
rightTitle: '',
isTipsImg: false,
isHaveRightWidget: true,
rightWidget: SizedBox(
width: 60.w,
height: 50.h,
child: _isAdmin(),
),
),
),
),
Container(height: 10.h),
CommonItem(
leftTitel: '操作记录'.tr,

View File

@ -461,6 +461,7 @@ class FingerprintListLogic extends BaseGetXController {
_initReplySubscription();
// _initRefreshAction();
await getFingerprintsListData(isRefresh: true);
}
}

View File

@ -15,6 +15,8 @@ import 'package:star_lock/main/lockDetail/lockDetail/device_network_info.dart';
import 'package:star_lock/main/lockDetail/lockSet/lockTime/getServerDatetime_entity.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/throttler.dart';
@ -564,7 +566,7 @@ class LockDetailLogic extends BaseGetXController {
// token
Future<void> getLockNetToken() async {
final LockNetTokenEntity entity = await ApiRepository.to
.getLockNetToken(lockId: state.keyInfos.value.lockId.toString());
.getLockNetToken(lockId: state.keyInfos.value.lockId!);
if (entity.errorCode!.codeIsSuccessful) {
state.lockNetToken = entity.data!.token!.toString();
// AppLog.log('从服务器获取联网token:${state.lockNetToken}');
@ -741,42 +743,40 @@ class LockDetailLogic extends BaseGetXController {
eventBus.fire(RefreshLockDetailInfoDataEvent());
}
///
void requestDeviceNetworkInfo() async {
final DeviceNetwork deviceNetworkInfo =
await ApiRepository.to.getDeviceNetwork(
deviceType: 2,
deviceMac: state.keyInfos.value.mac!,
);
if (deviceNetworkInfo.data?.wifiName == null ||
deviceNetworkInfo.data?.wifiName == '') {
return;
} else {
final peerId = deviceNetworkInfo?.data?.peerId;
if (peerId == null || peerId.isEmpty || peerId == '') {
throw Exception('设备peerId为空');
}
// peerID
StartChartManage().lockNetworkInfo =
deviceNetworkInfo.data ?? DeviceNetworkInfo();
StartChartManage().lockPeerId = peerId;
}
}
// ///
// void requestDeviceNetworkInfo() async {
// final DeviceNetwork deviceNetworkInfo =
// await ApiRepository.to.getDeviceNetwork(
// deviceType: 2,
// deviceMac: state.keyInfos.value.mac!,
// );
// if (deviceNetworkInfo.data?.peerId == null ||
// deviceNetworkInfo.data?.peerId == '') {
// return;
// }
// final peerId = deviceNetworkInfo!.data!.peerId;
// // peerID
// StartChartManage().lockNetworkInfo =
// deviceNetworkInfo.data ?? DeviceNetworkInfo();
// StartChartManage().lockPeerId = peerId!;
// }
///
void sendMonitorMessage() {
final catEyeConfig = state.keyInfos.value.lockSetting?.catEyeConfig ?? [];
final network = state.keyInfos.value.network;
if (catEyeConfig.isNotEmpty &&
catEyeConfig.length > 0 &&
catEyeConfig[0].catEyeMode != 0) {
if (StartChartManage().lockNetworkInfo.wifiName == null ||
StartChartManage().lockNetworkInfo.wifiName == '') {
if (network == null || network?.peerId == null || network?.peerId == '') {
showToast('设备未配网'.tr);
return;
}
//
PacketLossStatistics().reset();
// id
StartChartManage().startCallRequestMessageTimer(
ToPeerId: StartChartManage().lockPeerId ?? '');
StartChartManage()
.startCallRequestMessageTimer(ToPeerId: network!.peerId ?? '');
} else {
showToast('猫眼设置为省电模式时无法进行监控,请在猫眼设置中切换为其他模式'.tr);
}
@ -788,12 +788,13 @@ class LockDetailLogic extends BaseGetXController {
getServerDatetime();
await PermissionDialog.request(Permission.location);
await PermissionDialog.requestBluetooth();
requestDeviceNetworkInfo();
// requestDeviceNetworkInfo();
}
@override
void onInit() {
super.onInit();
state.LockSetChangeSetRefreshLockDetailWithTypeSubscription = eventBus
.on<LockSetChangeSetRefreshLockDetailWithType>()
.listen((LockSetChangeSetRefreshLockDetailWithType event) {
@ -840,11 +841,11 @@ class LockDetailLogic extends BaseGetXController {
}
});
state.SuccessfulDistributionNetworkEvent = eventBus
.on<SuccessfulDistributionNetwork>()
.listen((SuccessfulDistributionNetwork event) {
//
requestDeviceNetworkInfo();
});
// state.SuccessfulDistributionNetworkEvent = eventBus
// .on<SuccessfulDistributionNetwork>()
// .listen((SuccessfulDistributionNetwork event) {
// //
// requestDeviceNetworkInfo();
// });
}
}

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -88,7 +89,6 @@ class _LockDetailPageState extends State<LockDetailPage>
///
AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);
state.isOpenLockNeedOnline.refresh();
}
StreamSubscription? _lockRefreshLockDetailInfoDataEvent;
@ -1179,7 +1179,7 @@ class _LockDetailPageState extends State<LockDetailPage>
}
//->
if (state.keyInfos.value.lockFeature!.videoIntercom == 1) {
if (state.keyInfos.value.lockFeature!.isSupportCatEye == 1) {
showWidgetArr.add(
bottomItem('images/main/icon_catEyes.png', '监控'.tr,
state.bottomBtnisEable.value, () async {
@ -1467,7 +1467,7 @@ class _LockDetailPageState extends State<LockDetailPage>
state.iSOpenLock.value = true;
state.openLockBtnState.value = 1;
state.animationController!.forward();
// AppLog.log('点击开锁');
AppLog.log('点击开锁');
if (isOpenLockNeedOnline) {
//
state.openDoorModel = 0;

View File

@ -7,18 +7,18 @@ import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dar
import '../../../blue/io_reply.dart';
import '../../lockMian/entity/lockListInfo_entity.dart';
class LockDetailState {
Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs;
final Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
late StreamSubscription<Reply> replySubscription;
StreamSubscription? lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent;
StreamSubscription?
lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent;
StreamSubscription? LockSetChangeSetRefreshLockDetailWithTypeSubscription;
StreamSubscription? DetailLockInfo;
StreamSubscription? SuccessfulDistributionNetworkEvent;
String lockNetToken = '0';
int differentialTime = 0;//
int differentialTime = 0; //
bool isHaveNetwork = true;
int lockUserNo = 0;
int senderUserId = 0;
@ -41,7 +41,7 @@ class LockDetailState {
RxBool bottomBtnisEable = true.obs; //
RxBool openDoorBtnisUneable = true.obs; // 使使,
int openDoorModel = 0;// 线0, 线2 线32 线34
int openDoorModel = 0; // 线0, 线2 线32 线34
//
AnimationController? animationController;

View File

@ -37,40 +37,46 @@ import 'configuringWifi_state.dart';
class ConfiguringWifiLogic extends BaseGetXController {
final ConfiguringWifiState state = ConfiguringWifiState();
final int _configurationTimeout = 60; //
/// WiFi锁服务IP和端口
Future<void> getWifiLockServiceIpAndPort() async {
final ConfiguringWifiEntity entity =
await ApiRepository.to.getWifiLockServiceIpAndPort();
if (entity.errorCode! == 0) {
state.configuringWifiEntity.value = entity;
try {
final ConfiguringWifiEntity entity =
await ApiRepository.to.getWifiLockServiceIpAndPort();
if (entity.errorCode! == 0) {
state.configuringWifiEntity.value = entity;
} else {
AppLog.log('获取WiFi锁服务IP和端口失败${entity.errorCode}');
}
} catch (e) {
AppLog.log('获取WiFi锁服务IP和端口异常$e');
}
}
void updateNetworkInfo({
///
Future<LoginEntity> updateNetworkInfo({
required String peerId,
required String wifiName,
required String secretKey,
required String deviceMac,
required String networkMac,
}) async {
final LoginEntity entity = await ApiRepository.to.settingDeviceNetwork(
deviceType: 2,
deviceMac: deviceMac,
wifiName: wifiName,
networkMac: networkMac,
secretKey: secretKey,
peerId: peerId,
);
if (entity.errorCode!.codeIsSuccessful) {
// peerID
StartChartManage().lockNetworkInfo = DeviceNetworkInfo(
try {
final LoginEntity entity = await ApiRepository.to.settingDeviceNetwork(
deviceType: 2,
deviceMac: deviceMac,
wifiName: wifiName,
networkMac: networkMac,
secretKey: secretKey,
peerId: peerId,
);
await _getUploadLockSet();
return entity;
} catch (e) {
dismissEasyLoading();
state.sureBtnState.value = 0;
AppLog.log('网络配置异常:$e');
return LoginEntity();
}
}
@ -84,81 +90,164 @@ class ConfiguringWifiLogic extends BaseGetXController {
if (reply is GatewayConfiguringWifiResultReply) {
_replySenderConfiguringWifiResult(reply);
}
// wifi配网命令应答结果
if (reply is GatewayConfiguringWifiReply) {
_replySenderConfiguringWifiResult(reply);
_replySenderConfiguringWifi(reply);
}
if (reply is GatewayGetStatusReply) {
_replyGatewayGetStatusReply(reply);
}
// if (reply is GatewayGetStatusReply) {
// _replyStatusInfo(reply);
// }
//
if (reply is UpdataLockSetReply) {
_replyUpdataLockSetReply(reply);
}
AppLog.log('蓝牙回调处理完毕${EasyLoading.isShow}');
});
}
// WIFI配网结果
Future<void> _replySenderConfiguringWifiResult(Reply reply) async {
// WIFI配网操作结果处理
Future<void> _replySenderConfiguringWifi(Reply reply) async {
final int status = reply.data[2];
// state.sureBtnState.value = 0;
// loading超时定时器
state.loadingTimer?.cancel();
state.loadingTimer = null;
switch (status) {
case 0x00:
await Storage.removeLockNetWorkInfoCache();
final int secretKeyJsonLength = (reply.data[4] << 8) + reply.data[3];
final List<int> secretKeyList =
reply.data.sublist(5, 5 + secretKeyJsonLength);
String result = utf8String(secretKeyList);
// JSON Map
Map<String, dynamic> jsonMap = json.decode(result);
// peerId
String? peerId = jsonMap['peerId'];
String? wifiName = jsonMap['wifiName'];
String? secretKey = jsonMap['secretKey'];
String? deviceMac = jsonMap['deviceMac'];
String? networkMac = jsonMap['networkMac'];
/// ,peerId
StartChartManage().lockPeerId = peerId ?? '';
state.isLoading.value = false;
//
await Storage.saveLockNetWorkInfo(jsonMap);
//
updateNetworkInfo(
peerId: peerId ?? '',
wifiName: wifiName ?? '',
secretKey: secretKey ?? '',
deviceMac: deviceMac ?? '',
networkMac: networkMac ?? '');
AppLog.log('wifi配网命令回复结果:成功');
break;
default:
//
dismissEasyLoading(); // loading
cancelBlueConnetctToastTimer();
if (state.loadingTimer != null) {
state.loadingTimer!.cancel();
state.loadingTimer = null;
}
showToast('配网失败'.tr);
state.isLoading.value = false;
break;
}
}
// JSON
// WIFI配网结果处理
Future<void> _replySenderConfiguringWifiResult(Reply reply) async {
final int status = reply.data[2];
//
cancelBlueConnetctToastTimer();
switch (status) {
case 0x00:
// - loading
await Storage.removeLockNetWorkInfoCache();
try {
final int secretKeyJsonLength = (reply.data[4] << 8) + reply.data[3];
final List<int> secretKeyList =
reply.data.sublist(5, 5 + secretKeyJsonLength);
String result = utf8String(secretKeyList);
AppLog.log('解析配网信息: $result');
// JSON Map
Map<String, dynamic> jsonMap = json.decode(result);
//
String? peerId = jsonMap['peerId'];
String? wifiName = jsonMap['wifiName'];
String? secretKey = jsonMap['secretKey'];
String? deviceMac = jsonMap['deviceMac'];
String? networkMac = jsonMap['networkMac'];
//
if (peerId == null ||
peerId.isEmpty ||
secretKey == null ||
secretKey.isEmpty) {
throw Exception('Missing required network information');
}
// - : sureBtnState updateNetworkInfo
final info = await updateNetworkInfo(
peerId: peerId,
wifiName: wifiName ?? '',
secretKey: secretKey,
deviceMac: deviceMac ?? '',
networkMac: networkMac ?? '');
if (info.errorCode!.codeIsSuccessful) {
// peerID
StartChartManage().lockNetworkInfo = DeviceNetworkInfo(
wifiName: wifiName,
networkMac: networkMac,
secretKey: secretKey,
peerId: peerId,
);
/// ,peerId
StartChartManage().lockPeerId = peerId;
//
await Storage.saveLockNetWorkInfo(jsonMap);
showToast('配网成功'.tr, something: () {
state.sureBtnState.value = 0; //
if (state.pageName.value == 'lockSet') {
Get.close(2);
} else {
Get.offAllNamed(Routers.starLockMain);
}
eventBus.fire(SuccessfulDistributionNetwork());
});
//
_getUploadLockSet();
} else {
dismissEasyLoading();
// showToast('网络配置失败,请重试'.tr);
state.sureBtnState.value = 0;
}
} catch (e) {
if (EasyLoading.isShow) {
dismissEasyLoading();
}
// showToast('解析配网信息失败,请重试'.tr);
state.sureBtnState.value = 0; //
AppLog.log('解析配网信息失败: $e');
return; // return阻止后续流程
}
break;
case 0x01:
// WiFi密码错误
if (EasyLoading.isShow) {
dismissEasyLoading();
}
// showToast('WiFi密码错误请重新输入'.tr);
state.sureBtnState.value = 0; //
break;
case 0x02:
// WiFi
if (EasyLoading.isShow) {
dismissEasyLoading();
}
// showToast('找不到该WiFi网络请确认WiFi名称正确'.tr);
state.sureBtnState.value = 0; //
break;
case 0x03:
//
if (EasyLoading.isShow) {
dismissEasyLoading();
}
// showToast('连接WiFi超时请确保网络信号良好'.tr);
state.sureBtnState.value = 0; //
break;
default:
//
if (EasyLoading.isShow) {
dismissEasyLoading();
}
// showToast('配网失败 (错误码: $status),请重试'.tr);
state.sureBtnState.value = 0; //
break;
}
}
// JSON
String prettyPrintJson(String jsonString) {
var jsonObject = json.decode(jsonString);
return JsonEncoder.withIndent(' ').convert(jsonObject);
@ -168,80 +257,111 @@ class ConfiguringWifiLogic extends BaseGetXController {
Future<void> senderConfiguringWifiAction() async {
AppLog.log('开始配网${EasyLoading.isShow}');
if (state.isLoading.isTrue) {
if (state.sureBtnState.value == 1) {
AppLog.log('正在配网中请勿重复点击');
return;
}
if (state.wifiNameController.text.isEmpty) {
showToast('请输入wifi名称'.tr);
//
try {
final GetGatewayConfigurationEntity entity = await ApiRepository.to
.getGatewayConfigurationNotLoading(timeout: _configurationTimeout);
if (entity.errorCode!.codeIsSuccessful) {
state.getGatewayConfigurationStr = entity.data ?? '';
} else {
// showToast('获取网关配置失败,请重试'.tr);
AppLog.log('获取网关配置失败,请重试');
return;
}
//
final loginData = await Storage.getLoginData();
if (loginData == null) {
AppLog.log('未检测到登录信息,请重新登录'.tr);
return;
}
// app用户的peerId
String appPeerId = loginData.starchart?.starchartId ?? '';
if (appPeerId.isEmpty) {
AppLog.log('用户ID获取失败请重新登录'.tr);
return;
}
//
if (state.getGatewayConfigurationStr.isNotEmpty) {
// JSON Map
Map<String, dynamic> jsonMap =
json.decode(state.getGatewayConfigurationStr);
//
jsonMap.remove("starCloudUrl");
jsonMap.remove("starLockPeerId");
//
jsonMap['userPeerld'] = appPeerId;
// Map JSON
state.getGatewayConfigurationStr =
json.encode(jsonMap).replaceAll(',', ',\n');
//
state.getGatewayConfigurationStr =
prettyPrintJson(state.getGatewayConfigurationStr);
} else {
//
state.getGatewayConfigurationStr = "{\"userPeerld\": \"$appPeerId\"}";
}
} catch (e) {
AppLog.log('网关配置准备失败:${e.toString()}'.tr);
return;
}
if (state.wifiPWDController.text.isEmpty) {
showToast('请输入WiFi密码'.tr);
return;
}
// if (state.sureBtnState.value == 1) {
// return;
// }
// state.sureBtnState.value = 1;
// sureBtnState状态
state.sureBtnState.value = 1;
final GetGatewayConfigurationEntity entity =
await ApiRepository.to.getGatewayConfigurationNotLoading(timeout: 60);
if (entity.errorCode!.codeIsSuccessful) {
state.getGatewayConfigurationStr = entity.data ?? '';
// loading
if (!EasyLoading.isShow) {
showEasyLoading();
}
//
final loginData = await Storage.getLoginData();
// app用户的peerId
String appPeerId = loginData?.starchart?.starchartId ?? '';
//
if (state.getGatewayConfigurationStr.isNotEmpty) {
// JSON Map
Map<String, dynamic> jsonMap =
json.decode(state.getGatewayConfigurationStr);
//
jsonMap.remove("starCloudUrl");
jsonMap.remove("starLockPeerId");
//
jsonMap['userPeerld'] = appPeerId;
// Map JSON
state.getGatewayConfigurationStr =
json.encode(jsonMap).replaceAll(',', ',\n');
//
state.getGatewayConfigurationStr =
prettyPrintJson(state.getGatewayConfigurationStr);
} else {
//
state.getGatewayConfigurationStr = "{\"userPeerld\": \"$appPeerId\"}";
}
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
state.isLoading.value = false;
});
//
showBlueConnetctToastTimer(
action: () {
if (EasyLoading.isShow) {
dismissEasyLoading();
}
state.sureBtnState.value = 0; //
},
outTimer: 30,
);
//
BlueManage().blueSendData(
BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) {
IoSenderManage.gatewayConfiguringWifiCommand(
ssid: state.wifiNameController.text,
password: state.wifiPWDController.text,
gatewayConfigurationStr: state.getGatewayConfigurationStr,
);
try {
IoSenderManage.gatewayConfiguringWifiCommand(
ssid: state.wifiNameController.text,
password: state.wifiPWDController.text,
gatewayConfigurationStr: state.getGatewayConfigurationStr,
);
// sureBtnState状态
} catch (e) {
if (EasyLoading.isShow) {
dismissEasyLoading();
}
cancelBlueConnetctToastTimer();
state.sureBtnState.value = 0; //
// showToast('发送配网指令失败:${e.toString()}'.tr);
}
} else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
if (EasyLoading.isShow) {
dismissEasyLoading();
}
cancelBlueConnetctToastTimer();
state.isLoading.value = false;
// state.sureBtnState.value = 0;
state.sureBtnState.value = 0; //
if (state.ifCurrentScreen.value == true) {
showBlueConnetctToast();
}
@ -249,7 +369,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
},
isAddEquipment: false,
);
state.isLoading.value = true;
}
//
@ -278,11 +397,15 @@ class ConfiguringWifiLogic extends BaseGetXController {
final NetworkInfo _networkInfo = NetworkInfo();
Future<String> getWifiName() async {
String ssid = '';
ssid = (await _networkInfo.getWifiName())!;
ssid = ssid ?? '';
ssid = ssid.replaceAll(r'"', '');
return ssid ?? '';
try {
String? ssid = await _networkInfo.getWifiName();
ssid = ssid ?? '';
ssid = ssid.replaceAll(r'"', '');
return ssid;
} catch (e) {
AppLog.log('获取WiFi名称失败: $e');
return '';
}
}
///
@ -308,17 +431,12 @@ class ConfiguringWifiLogic extends BaseGetXController {
getWifiLockServiceIpAndPort();
_initReplySubscription();
// getDevicesStatusAction();
}
@override
void onInit() {
super.onInit();
}
@override
void onClose() {
_replySubscription.cancel();
cancelBlueConnetctToastTimer(); //
super.onClose();
}
@ -330,8 +448,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
switch (status) {
case 0x00:
//
// state.sureBtnState.value = 0;
final GetGatewayInfoModel gatewayModel = GetGatewayInfoModel();
// MAC地址
int index = 3;
@ -372,25 +488,16 @@ class ConfiguringWifiLogic extends BaseGetXController {
default:
//
dismissEasyLoading();
showToast('配网失败'.tr);
if (state.loadingTimer != null) {
state.loadingTimer!.cancel();
state.loadingTimer = null;
}
// showToast('获取设备状态失败'.tr);
break;
}
}
//
Future<void> _getUploadLockSet() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
final List<String>? token = await Storage.getStringList(saveBlueToken);
final List<int> getTokenList = changeStringListToIntList(token!);
//
await _uploadLockSet(getTokenList);
}
@ -398,44 +505,74 @@ class ConfiguringWifiLogic extends BaseGetXController {
Future<void> _uploadLockSet(List<int> token) async {
final List<String>? privateKey =
await Storage.getStringList(saveBluePrivateKey);
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
if (privateKey == null || privateKey.isEmpty) {
throw Exception('Private key is empty');
}
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey);
final List<String>? signKey = await Storage.getStringList(saveBlueSignKey);
final List<int> signKeyDataList = changeStringListToIntList(signKey!);
if (signKey == null || signKey.isEmpty) {
throw Exception('Sign key is empty');
}
final List<int> signKeyDataList = changeStringListToIntList(signKey);
IoSenderManage.updataLockSetCommand(
lockID: BlueManage().connectDeviceName,
userID: await Storage.getUid(),
token: token,
needAuthor: 1,
signKey: signKeyDataList,
privateKey: getPrivateKeyList);
BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) {
IoSenderManage.updataLockSetCommand(
lockID: BlueManage().connectDeviceName,
userID: await Storage.getUid(),
token: token,
needAuthor: 1,
signKey: signKeyDataList,
privateKey: getPrivateKeyList,
);
} else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
if (state.ifCurrentScreen.value == true) {
showBlueConnetctToast();
}
}
}, isAddEquipment: true);
}
//
Future<void> _replyUpdataLockSetReply(Reply reply) async {
final int status = reply.data[2];
dismissEasyLoading(); // loading
// loading状态直到整个过程完成
cancelBlueConnetctToastTimer();
switch (status) {
case 0x00:
await _lockDataUpload(
uploadType: 1,
recordType: 0,
records: reply.data.sublist(7, reply.data.length));
break;
case 0x06:
//
final List<int> token = reply.data.sublist(3, 7);
final List<String> saveStrList = changeIntListToStringList(token);
Storage.setStringList(saveBlueToken, saveStrList);
_uploadLockSet(token);
//token
try {
final List<int> token = reply.data.sublist(3, 7);
final List<String> saveStrList = changeIntListToStringList(token);
await Storage.setStringList(saveBlueToken, saveStrList);
_uploadLockSet(token);
} catch (e) {
if (EasyLoading.isShow) {
dismissEasyLoading(); // loading
}
// showToast('获取设置权限失败:${e.toString()}'.tr);
state.sureBtnState.value = 0; //
}
break;
default:
dismissEasyLoading();
cancelBlueConnetctToastTimer();
if (EasyLoading.isShow) {
dismissEasyLoading(); // loading
}
// showToast('获取锁设置失败 (错误码: $status)'.tr);
state.sureBtnState.value = 0; //
break;
}
}
@ -451,19 +588,10 @@ class ConfiguringWifiLogic extends BaseGetXController {
recordType: recordType,
records: records,
isUnShowLoading: true);
if (entity.errorCode!.codeIsSuccessful) {
showToast('配网成功'.tr, something: () {
state.isLoading.value = false;
if (state.pageName.value == 'lockSet') {
Get.close(2);
} else {
Get.offAllNamed(Routers.starLockMain);
}
eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
eventBus.fire(SuccessfulDistributionNetwork());
});
if (entity.errorCode!.codeIsSuccessful) {
eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
}
}
}

View File

@ -25,6 +25,9 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
final ConfiguringWifiLogic logic = Get.put(ConfiguringWifiLogic());
final ConfiguringWifiState state = Get.find<ConfiguringWifiLogic>().state;
//
final RxBool _obscureText = true.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -39,19 +42,36 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
'WiFi名称'.tr, '请输入WiFi名字'.tr, state.wifiNameController),
Container(
width: 1.sw, height: 1.h, color: AppColors.mainBackgroundColor),
configuringWifiTFWidget(
configuringWifiPasswordTFWidget(
'WiFi密码'.tr, '请输入WiFi密码'.tr, state.wifiPWDController),
SizedBox(
height: 50.h,
),
Obx(
() => SubmitBtn(
btnName: '确定'.tr,
isDisabled: state.isLoading.isFalse,
onClick: state.isLoading.isTrue
btnName: state.sureBtnState.value == 1 ? '配置中...'.tr : '确定'.tr,
// sureBtnState为1时按钮不可用
isDisabled: state.sureBtnState.value == 0,
onClick: state.sureBtnState.value == 1
? null
: () {
FocusScope.of(context).requestFocus(FocusNode());
//
if (state.wifiNameController.text.isEmpty) {
logic.showToast('请输入WiFi名称'.tr);
return;
}
if (state.wifiPWDController.text.isEmpty) {
logic.showToast('请输入WiFi密码'.tr);
return;
}
// WiFi名称是否包含5G关键字
if (state.wifiNameController.text
.toLowerCase()
.contains('5g')) {
logic.showToast('请确保使用2.4GHz WiFi网络'.tr);
return;
}
logic.senderConfiguringWifiAction();
},
),
@ -86,7 +106,22 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
);
}
//
Widget configuringWifiPasswordTFWidget(
String titleStr, String rightTitle, TextEditingController controller) {
return Column(
children: <Widget>[
Container(height: 10.h),
CommonItem(
leftTitel: titleStr,
rightTitle: '',
isHaveRightWidget: true,
rightWidget: getPasswordTFWidget(rightTitle, controller)),
Container(height: 10.h),
],
);
}
//
Widget getTFWidget(String tfStr, TextEditingController controller) {
return Container(
height: 65.h,
@ -95,18 +130,14 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
children: <Widget>[
Expanded(
child: TextField(
//
maxLines: 1,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.deny('\n'),
// LengthLimitingTextInputFormatter(30),
],
controller: controller,
autofocus: false,
textAlign: TextAlign.end,
decoration: InputDecoration(
//
// contentPadding: const EdgeInsets.only(top: 12.0, bottom: 8.0),
hintText: tfStr,
hintStyle: TextStyle(fontSize: 22.sp),
focusedBorder: const OutlineInputBorder(
@ -135,6 +166,61 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
);
}
//
Widget getPasswordTFWidget(String tfStr, TextEditingController controller) {
return Container(
height: 65.h,
width: 300.w,
child: Row(
children: <Widget>[
Expanded(
child: Obx(
() => TextField(
maxLines: 1,
obscureText: _obscureText.value,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.deny('\n'),
],
controller: controller,
autofocus: false,
textAlign: TextAlign.end,
decoration: InputDecoration(
hintText: tfStr,
hintStyle: TextStyle(fontSize: 22.sp),
focusedBorder: const OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
disabledBorder: const OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
enabledBorder: const OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
border: const OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
style: TextStyle(
fontSize: 22.sp, textBaseline: TextBaseline.alphabetic),
),
),
),
IconButton(
icon: Icon(
_obscureText.value ? Icons.visibility_off : Icons.visibility,
color: Colors.grey,
size: 24.sp,
),
onPressed: () {
_obscureText.value = !_obscureText.value;
},
),
],
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();

View File

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:network_info_plus/network_info_plus.dart';
@ -33,5 +31,4 @@ class ConfiguringWifiState {
String getGatewayConfigurationStr = '';
RxBool isLoading = false.obs;
Timer? loadingTimer;
}

View File

@ -2,6 +2,7 @@ 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/io_gateway/io_gateway_getWifiList.dart';
import 'package:star_lock/blue/io_protocol/io_getWifiList.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
@ -20,7 +21,9 @@ class WifiListLogic extends BaseGetXController {
//
late StreamSubscription<Reply> _replySubscription;
Timer? _connectionTimer;
///
void _initReplySubscription() {
_replySubscription =
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
@ -31,78 +34,130 @@ class WifiListLogic extends BaseGetXController {
if (reply is GatewayGetWifiListReply) {
_replyGetWifiListParameters(reply);
}
}, onError: (error) {
// CRC校验失败等错误
AppLog.log('WiFi列表获取过程中发生错误: $error');
// loading状态
dismissEasyLoading();
cancelBlueConnetctToastTimer();
//
state.sureBtnState.value = 0;
// CRC校验失败
if (error.toString().contains('CRC')) {
showToast('数据校验失败,请重新扫描'.tr);
} else {
showToast('扫描WiFi失败请重试'.tr);
}
});
}
// wifi列表数据解析
/// wifi列表数据解析
Future<void> _replySendGetWifiParameters(Reply reply) async {
final int status = reply.data[2];
switch (status) {
case 0x00:
//
showEasyLoading();
// - loading框UI中已经有进度指示器
cancelBlueConnetctToastTimer();
Future.delayed(5.seconds, dismissEasyLoading);
break;
case 0x06:
//
dismissEasyLoading();
AppLog.log('需要设备鉴权,请重试'.tr);
state.sureBtnState.value = 0;
break;
default:
//
dismissEasyLoading();
AppLog.log('获取WiFi列表失败错误码$status'.tr);
state.sureBtnState.value = 0;
break;
}
}
// WiFi数据解析
/// WiFi数据解析
Future<void> _replyGetWifiListParameters(Reply reply) async {
final int status = reply.data[2];
switch (status) {
case 0x00:
//
// showEasyLoading();
dismissEasyLoading();
state.sureBtnState.value = 0;
if (reply.data[3] > 0) {
reply.data.removeRange(0, 4);
// 33
// 33
final List<List<int>> getList = splitList(reply.data, 33);
final List<Map<String, String>> uploadList = <Map<String, String>>[];
for (int i = 0; i < getList.length; i++) {
final List<int> indexList = getList[i];
final Map<String, String> indexMap = <String, String>{};
final List<int> wifiName = indexList.sublist(0, 32);
indexMap['wifiName'] = utf8String(wifiName);
final String wifiNameStr = utf8String(wifiName).trim();
// WiFi名称
if (wifiNameStr.isEmpty) {
continue;
}
indexMap['wifiName'] = wifiNameStr;
indexMap['rssi'] = (indexList.last - 255).toString();
uploadList.add(indexMap);
state.wifiNameDataList.value = uploadList;
}
// WiFi列表 ()
uploadList.sort(
(a, b) => int.parse(b['rssi']!).compareTo(int.parse(a['rssi']!)));
state.wifiNameDataList.value = uploadList;
if (uploadList.isEmpty) {
showToast('未检测到可用的WiFi网络'.tr);
}
} else {
// WiFi列表为空的情况
state.wifiNameDataList.clear();
showToast('未检测到可用的WiFi网络'.tr);
}
break;
default:
//
dismissEasyLoading();
showToast('解析WiFi列表失败错误码$status'.tr);
state.sureBtnState.value = 0;
break;
}
}
// wifi列表
/// WiFi列表
Future<void> senderGetWifiListWifiAction() async {
if (state.sureBtnState.value == 1) {
return;
}
state.sureBtnState.value = 1;
state.wifiNameDataList.clear(); //
showEasyLoading();
// loading框UI中已经有进度指示器
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
state.sureBtnState.value = 0;
});
BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) {
IoSenderManage.gatewayGetWifiCommand(
userID: await Storage.getUid(),
);
try {
IoSenderManage.gatewayGetWifiCommand(
userID: await Storage.getUid(),
);
} catch (e) {
state.sureBtnState.value = 0;
cancelBlueConnetctToastTimer();
showToast('发送获取WiFi列表请求失败${e.toString()}'.tr);
}
} else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
state.sureBtnState.value = 0;
cancelBlueConnetctToastTimer();
if (state.ifCurrentScreen.value == true) {
@ -113,22 +168,27 @@ class WifiListLogic extends BaseGetXController {
}
@override
void onReady() {
void onReady() async {
super.onReady();
_initReplySubscription();
await senderGetWifiListWifiAction();
dismissEasyLoading();
}
@override
void onInit() {
super.onInit();
senderGetWifiListWifiAction();
//
state.ifCurrentScreen.value = true;
}
@override
void onClose() {
super.onClose();
//
_replySubscription.cancel();
_connectionTimer?.cancel();
cancelBlueConnetctToastTimer();
state.ifCurrentScreen.value = false;
super.onClose();
}
}

View File

@ -23,6 +23,32 @@ class _WifiListPageState extends State<WifiListPage> {
final WifiListLogic logic = Get.put(WifiListLogic());
final WifiListState state = Get.find<WifiListLogic>().state;
/// WiFi信号强度图标
IconData _getWifiSignalIcon(String rssi) {
final int rssiValue = int.parse(rssi);
if (rssiValue >= -50) {
return Icons.signal_wifi_4_bar;
} else if (rssiValue >= -70) {
return Icons.network_wifi;
} else if (rssiValue >= -80) {
return Icons.network_wifi_2_bar_rounded;
} else {
return Icons.signal_wifi_0_bar;
}
}
@override
void initState() {
super.initState();
state.ifCurrentScreen.value = true;
}
@override
void dispose() {
state.ifCurrentScreen.value = false;
super.dispose();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
@ -38,13 +64,15 @@ class _WifiListPageState extends State<WifiListPage> {
barTitle: 'WIFI列表'.tr,
haveBack: state.pageName.value == 'lockSet',
actionsList: <Widget>[
TextButton(
child: Text(
'刷新'.tr,
style: TextStyle(color: Colors.white, fontSize: 24.sp),
),
onPressed: logic.senderGetWifiListWifiAction,
),
Obx(() => TextButton(
child: Text(
'刷新'.tr,
style: TextStyle(color: Colors.white, fontSize: 24.sp),
),
onPressed: state.sureBtnState.value == 0
? logic.senderGetWifiListWifiAction
: null,
)),
],
backgroundColor: AppColors.mainColor,
),
@ -52,26 +80,56 @@ class _WifiListPageState extends State<WifiListPage> {
children: <Widget>[
Expanded(
child: Obx(() => state.wifiNameDataList.value.isNotEmpty
? ListView.builder(
itemCount: state.wifiNameDataList.value.length,
itemBuilder: (BuildContext c, int index) {
Map wifiNameStr = state.wifiNameDataList.value[index];
return _messageListItem(
wifiNameStr['wifiName'], wifiNameStr['rssi'], () {
Get.toNamed(Routers.configuringWifiPage,
arguments: {
'lockSetInfoData':
state.lockSetInfoData.value,
'wifiName': wifiNameStr['wifiName'],
'pageName': state.pageName.value,
});
});
})
: NoData(
noDataHeight: 1.sh -
ScreenUtil().statusBarHeight -
ScreenUtil().bottomBarHeight -
64.h)),
? RefreshIndicator(
onRefresh: () async {
if (state.sureBtnState.value == 0) {
await logic.senderGetWifiListWifiAction();
}
},
child: ListView.builder(
itemCount: state.wifiNameDataList.value.length,
itemBuilder: (BuildContext c, int index) {
Map wifiNameStr =
state.wifiNameDataList.value[index];
return _messageListItem(
wifiNameStr['wifiName'], wifiNameStr['rssi'],
() {
Get.toNamed(Routers.configuringWifiPage,
arguments: {
'lockSetInfoData':
state.lockSetInfoData.value,
'wifiName': wifiNameStr['wifiName'],
'pageName': state.pageName.value,
});
});
}),
)
: Obx(() => state.sureBtnState.value == 1
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 50.w,
height: 50.w,
child: CircularProgressIndicator(
strokeWidth: 4.w,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.mainColor,
),
backgroundColor: Colors.grey[200],
),
),
SizedBox(height: 20.h),
Text('正在扫描WiFi网络...\n请确保设备处于正常状态'.tr,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24.sp,
color: AppColors.blackColor))
],
),
)
: NoData())),
),
state.pageName.value == 'saveLock'
? SubmitBtn(
@ -140,24 +198,37 @@ class _WifiListPageState extends State<WifiListPage> {
height: 79.h,
width: 1.sw - 20.w * 2,
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
flex: 4,
child: Text(
'$wifiName(${rssi}db)',
wifiName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 22.sp, color: AppColors.blackColor),
fontSize: 24.sp, color: AppColors.blackColor),
),
),
// Text(
// rssi,
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// style: TextStyle(
// fontSize: 22.sp, color: AppColors.blackColor),
// )
Flexible(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
_getWifiSignalIcon(rssi),
color: AppColors.mainColor,
size: 24.sp,
),
SizedBox(width: 8.w),
Text(
'$rssi dB',
style:
TextStyle(fontSize: 18.sp, color: Colors.black),
),
],
),
)
],
),
),

View File

@ -29,9 +29,9 @@ class CoerceOpenDoorLogic extends BaseGetXController {
case 2:
return '密码'.tr;
case 3:
return '指纹'.tr;
return 'IC卡'.tr;
case 4:
return ''.tr;
return '指纹'.tr;
case 5:
return '人脸'.tr;
default:

View File

@ -234,6 +234,7 @@ class PalmListLogic extends BaseGetXController {
_initReplySubscription();
// _initRefreshAction();
await getPalmListData(isRefresh: true);
}
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyDetail/passwordKeyDetail_logic.dart';
import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyDetail/passwordKeyDetail_state.dart';
import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyList/passwordKeyListEntity.dart';
@ -133,7 +134,7 @@ class _PasswordKeyDetailPageState extends State<PasswordKeyDetailPage>
action: () {}),
Container(height: 10.h),
Obx(() => Visibility(
visible: state.itemData.value.isCustom! == 1,
visible: state.itemData.value.isCustom! == 1 && !F.isSKY,
child: Column(
children: <Widget>[
CommonItem(

View File

@ -374,6 +374,9 @@ class LockFeature {
this.isNoSupportedBlueBroadcast,
this.wifiLockType,
this.wifi,
this.isH264,
this.isH265,
this.isMJpeg,
});
LockFeature.fromJson(Map<String, dynamic> json) {
@ -391,6 +394,9 @@ class LockFeature {
isNoSupportedBlueBroadcast = json['isNoSupportedBlueBroadcast'];
wifiLockType = json['wifiLockType'];
wifi = json['wifi'];
isH264 = json['isH264'];
isH265 = json['isH265'];
isMJpeg = json['isMJpeg'];
}
int? password;
@ -407,6 +413,9 @@ class LockFeature {
int? isNoSupportedBlueBroadcast;
int? wifiLockType;
int? wifi;
int? isH264;
int? isH265;
int? isMJpeg;
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
@ -424,6 +433,9 @@ class LockFeature {
data['isNoSupportedBlueBroadcast'] = isNoSupportedBlueBroadcast;
data['wifiLockType'] = wifiLockType;
data['wifi'] = wifi;
data['isH264'] = isH264;
data['isH265'] = isH265;
data['isMJpeg'] = isMJpeg;
return data;
}
}

View File

@ -60,9 +60,9 @@ class LockListLogic extends BaseGetXController {
//
void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) {
this.entity = entity;
if (entity.pageNo == 1) {
// if (entity.pageNo == 1) {
_groupDataList = <GroupList>[];
}
// }
_groupDataList.addAll(entity.groupList!);
update();
}

View File

@ -1,5 +1,7 @@
import 'dart:async';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:star_lock/tools/baseGetXController.dart';
import '../../../network/api_repository.dart';
import '../../../tools/eventBusEventManage.dart';
@ -46,6 +48,8 @@ class MessageListLogic extends BaseGetXController {
if (entity.errorCode!.codeIsSuccessful) {
pageNo = 1;
messageListDataRequest();
//
FlutterAppBadger.removeBadge();
}
}

View File

@ -353,7 +353,7 @@ class ApiProvider extends BaseProvider {
);
// token
Future<Response> getLockNetToken(String lockId) => post(
Future<Response> getLockNetToken(int lockId) => post(
getLockNetTokenURL.toUrl,
jsonEncode({
'lockId': lockId,

View File

@ -325,7 +325,7 @@ class ApiRepository {
}
// token
Future<LockNetTokenEntity> getLockNetToken({required String lockId}) async {
Future<LockNetTokenEntity> getLockNetToken({required int lockId}) async {
final res = await apiProvider.getLockNetToken(lockId);
return LockNetTokenEntity.fromJson(res.body);
}

View File

@ -16,4 +16,8 @@ class TalkConstant {
videoType: [VideoTypeE.H264],
audioType: [AudioTypeE.G711],
);
static TalkExpectReq H264_720P_Expect = TalkExpectReq(
videoType: [VideoTypeE.H264_720P],
audioType: [AudioTypeE.G711],
);
}

View File

@ -68,6 +68,18 @@ class ScpMessage {
return 'ScpMessage{ProtocolFlag: $ProtocolFlag, MessageType: $MessageType, MessageId: $MessageId, SpTotal: $SpTotal, SpIndex: $SpIndex, FromPeerId: $FromPeerId, ToPeerId: $ToPeerId, PayloadType: $PayloadType, PayloadCRC: $PayloadCRC, PayloadLength: $PayloadLength, Payload: $Payload}';
}
//
List<int> encodeFixedLengthString(String? str, int length) {
final bytes = utf8.encode(str ?? '');
if (bytes.length > length) {
return bytes.sublist(0, length);
} else if (bytes.length < length) {
return bytes + List.filled(length - bytes.length, 0);
} else {
return bytes;
}
}
String serialize() {
final bytes = <int>[];
@ -98,16 +110,19 @@ class ScpMessage {
if (SpIndex != null) {
bytes.add(SpIndex!);
}
// FromPeerId ()
// FromPeerId (44)
if (FromPeerId != null) {
bytes.addAll(utf8.encode(FromPeerId!));
}
// FromPeerId (44)
// bytes.addAll(encodeFixedLengthString(FromPeerId, 44));
// ToPeerId (32)
// ToPeerId (44)
if (ToPeerId != null) {
bytes.addAll(utf8.encode(ToPeerId!));
}
// ToPeerId (44)
// bytes.addAll(encodeFixedLengthString(ToPeerId, 44));
// PayloadType (2 bytes)
if (PayloadType != null) {

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
@ -22,7 +23,7 @@ class UdpEchoTestHandler extends ScpMessageBaseHandle
EasyLoading.showToast(scpMessage.Payload, duration: 2000.milliseconds);
} else {
talkDataRepository.addTalkData(
TalkData(content: payload, contentType: TalkData_ContentTypeE.Image));
TalkDataModel(talkData: TalkData(content: payload, contentType: TalkData_ContentTypeE.Image)));
}
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:get/get.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
@ -13,6 +14,7 @@ import 'package:star_lock/talk/starChart/proto/gateway_reset.pb.dart';
import 'package:star_lock/talk/starChart/proto/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_accept.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import '../../star_chart_manage.dart';
@ -76,8 +78,26 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
}
}
///
void _handleSendExpect() {
//
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
final isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
// 使H264MJPEG
if (isH264) {
// H264H264视频和G711音频期望
startChartManage.sendH264VideoAndG711AudioTalkExpectData();
print('锁支持H264发送H264视频格式期望数据');
} else if (isMJpeg) {
// MJPEGG711音频期望
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
print('锁不支持H264支持MJPEG发送MJPEG视频格式期望数据');
} else {
// 使
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
print('锁不支持H264和MJPEG默认发送图像视频格式期望数据');
}
}
}

View File

@ -5,6 +5,7 @@ import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
@ -61,9 +62,6 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
int? spTotal,
int? spIndex,
int? messageId}) {
//
final stats = PacketLossStatistics().getStatistics();
_asyncLog('丢包统计: $stats');
// _asyncLog(
// '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength');
if (messageType == MessageTypeConstant.RealTimeData) {
@ -118,7 +116,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
void _handleVideoH264(TalkData talkData) {
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
talkDataH264Frame.mergeFromBuffer(talkData.content);
frameHandler.handleFrame(talkDataH264Frame);
frameHandler.handleFrame(talkDataH264Frame, talkData);
}
///
@ -127,7 +125,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
await _processCompletePayload(Uint8List.fromList(talkData.content));
processCompletePayload.forEach((element) {
talkData.content = element;
talkDataRepository.addTalkData(talkData);
talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
});
}
@ -138,7 +140,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
// // pcm数据
// List<int> pcmBytes = G711().convertList(g711Data);
// talkData.content = pcmBytes;
talkDataRepository.addTalkData(talkData);
talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
} catch (e) {
print('Error decoding G.711 to PCM: $e');
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
@ -20,7 +21,7 @@ import '../../star_chart_manage.dart';
class UdpTalkExpectHandler extends ScpMessageBaseHandle
implements ScpMessageHandler {
final TalkViewState talkViewState = Get.put(TalkViewLogic()).state;
// final TalkViewState talkViewState = Get.put(TalkViewLogic()).state;
@override
void handleReq(ScpMessage scpMessage) {
@ -40,7 +41,9 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle
startChartManage.stopTalkExpectMessageTimer();
//
startChartManage.stopCallRequestMessageTimer();
talkViewState.rotateAngle.value = talkExpectResp.rotate ?? 0;
// talkViewState.rotateAngle.value = talkExpectResp.rotate ?? 0;
startChartManage.rotateAngle = talkExpectResp.rotate;
AppLog.log('视频画面需要旋转:${talkExpectResp.rotate}');
//
// x秒内没有收到通话保持则执行的操作;
talkePingOverTimeTimerManager.start();

View File

@ -1,21 +1,19 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/gateway_reset.pb.dart';
import 'package:star_lock/talk/starChart/proto/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_request.pb.dart';
import 'package:star_lock/tools/commonDataManage.dart';
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';
@ -25,27 +23,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
RxString currentLanguage =
CurrentLocaleTool.getCurrentLocaleString().obs; //
//
int _lastRequestTime = 0;
@override
void handleReq(ScpMessage scpMessage) async {
final currentTime = DateTime.now().millisecondsSinceEpoch;
// 1
if (currentTime - _lastRequestTime < 1000) {
// 1
replyErrorMessage(scpMessage);
AppLog.log('对讲请求过于频繁,已拒绝');
return;
}
//
_lastRequestTime = currentTime;
//
final loginData = await Storage.getLoginData();
//
if (loginData != null &&
(talkStatus.status != TalkStatus.passiveCallWaitingAnswer ||
@ -75,6 +56,8 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
//
startChartManage.FromPeerId = scpMessage.ToPeerId!;
startChartManage.ToPeerId = scpMessage.FromPeerId!;
//
_handleResponseSendExpect();
//
startChartManage.startTalkExpectTimer();
//
@ -97,26 +80,24 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
//
void _talkRequestEvent({required String talkObjectName}) {
//
_handleSendExpect();
_handleRequestSendExpect();
//
//test:使
playRingtone();
//
_showTalkRequestNotification(talkObjectName: talkObjectName);
// _showTalkRequestNotification(talkObjectName: talkObjectName);
//
talkStatus.setPassiveCallWaitingAnswer();
//
if (startChartManage
.getDefaultTalkExpect()
.videoType
.indexOf(VideoTypeE.H264) ==
-1) {
.getDefaultTalkExpect()
.videoType
.contains(VideoTypeE.H264)) {
Get.toNamed(
Routers.starChartTalkView,
Routers.h264WebView,
);
} else {
Get.toNamed(
Routers.h264WebView,
Routers.starChartTalkView,
);
}
}
@ -188,8 +169,49 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
}
}
void _handleSendExpect() {
//
startChartManage.sendOnlyImageVideoTalkExpectData();
/// app收到的对讲请求后
void _handleRequestSendExpect() {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
final isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
// 使H264MJPEG
if (isH264) {
// H264H264视频和G711音频期望
startChartManage.sendOnlyH264VideoTalkExpectData();
print('app收到的对讲请求后发送的预期数据=========锁支持H264发送H264视频格式期望数据');
} else if (isMJpeg) {
// MJPEGG711音频期望
startChartManage.sendOnlyImageVideoTalkExpectData();
print('app收到的对讲请求后发送的预期数据=========锁不支持H264支持MJPEG发送MJPEG视频格式期望数据');
} else {
// 使
startChartManage.sendOnlyImageVideoTalkExpectData();
print('app收到的对讲请求后发送的预期数据=========锁不支持H264和MJPEG默认发送图像视频格式期望数据');
}
}
/// app主动发请求
void _handleResponseSendExpect() {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
final isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
// 使H264MJPEG
if (isH264) {
// H264H264视频和G711音频期望
startChartManage.sendH264VideoAndG711AudioTalkExpectData();
print('app主动发请求收到回复后发送的预期数据=======锁支持H264发送H264视频格式期望数据');
} else if (isMJpeg) {
// MJPEGG711音频期望
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
print('app主动发请求收到回复后发送的预期数据=======锁不支持H264支持MJPEG发送MJPEG视频格式期望数据');
} else {
// 使
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
print('app主动发请求收到回复后发送的预期数据=======锁不支持H264和MJPEG默认发送图像视频格式期望数据');
}
}
}

View File

@ -3,15 +3,17 @@ import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import '../../proto/talk_data_h264_frame.pb.dart';
class H264FrameHandler {
final void Function(List<int> frameData) onCompleteFrame;
final void Function(TalkDataModel frameData) onCompleteFrame;
H264FrameHandler({required this.onCompleteFrame});
void handleFrame(TalkDataH264Frame frame) {
onCompleteFrame(frame.frameData);
void handleFrame(TalkDataH264Frame frame, TalkData talkData) {
onCompleteFrame(
TalkDataModel(talkData: talkData, talkDataH264Frame: frame));
}
}

View File

@ -10,6 +10,10 @@ class PacketLossStatistics {
// key: messageId, value: {totalPackets, receivedPackets}
final Map<int, PacketInfo> _packetsMap = HashMap();
//
int _maxCapacity = 300; // 300
int _timeoutMs = 30000; // 30
//
int _totalMessages = 0; //
int _lostMessages = 0; //
@ -18,10 +22,19 @@ class PacketLossStatistics {
//
void recordPacket(int messageId, int currentIndex, int totalPackets) {
//
_cleanupExpiredPackets();
//
_checkCapacityLimit();
if (!_packetsMap.containsKey(messageId)) {
_packetsMap[messageId] = PacketInfo(totalPackets);
_totalMessages++;
_totalPackets += totalPackets;
} else {
//
_packetsMap[messageId]!.timestamp = DateTime.now().millisecondsSinceEpoch;
}
_packetsMap[messageId]!.receivedPackets.add(currentIndex);
@ -32,6 +45,51 @@ class PacketLossStatistics {
}
}
//
void _cleanupExpiredPackets() {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final expiredMessageIds = <int>[];
_packetsMap.forEach((messageId, info) {
//
if (currentTime - info.timestamp > _timeoutMs) {
expiredMessageIds.add(messageId);
//
_lostMessages++;
_lostPackets += (info.totalPackets - info.receivedPackets.length);
}
});
//
for (var messageId in expiredMessageIds) {
_packetsMap.remove(messageId);
}
}
//
void _checkCapacityLimit() {
if (_packetsMap.length <= _maxCapacity) {
return;
}
//
var entries = _packetsMap.entries.toList()
..sort((a, b) => a.value.timestamp.compareTo(b.value.timestamp));
// 25%
int removeCount = (_packetsMap.length * 0.25).ceil();
//
for (int i = 0; i < removeCount && i < entries.length; i++) {
var entry = entries[i];
_lostMessages++;
_lostPackets +=
(entry.value.totalPackets - entry.value.receivedPackets.length);
_packetsMap.remove(entry.key);
}
}
//
void _checkPacketLoss(int messageId) {
final info = _packetsMap[messageId]!;
@ -62,6 +120,28 @@ class PacketLossStatistics {
return PacketLossInfo(messageLossRate, packetLossRate);
}
// Getter和Setter
int get maxCapacity => _maxCapacity;
set maxCapacity(int value) {
if (value > 0) {
_maxCapacity = value;
//
_checkCapacityLimit();
}
}
int get timeoutMs => _timeoutMs;
set timeoutMs(int value) {
if (value > 0) {
_timeoutMs = value;
//
_cleanupExpiredPackets();
}
}
//
int get pendingRecordsCount => _packetsMap.length;
//
void reset() {
_packetsMap.clear();
@ -76,8 +156,10 @@ class PacketLossStatistics {
class PacketInfo {
final int totalPackets;
final Set<int> receivedPackets = HashSet<int>();
int timestamp; //
PacketInfo(this.totalPackets);
PacketInfo(this.totalPackets)
: timestamp = DateTime.now().millisecondsSinceEpoch;
}
//

View File

@ -0,0 +1,9 @@
import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart';
import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
class TalkDataModel {
TalkData? talkData;
TalkDataH264Frame? talkDataH264Frame;
TalkDataModel({required this.talkData, this.talkDataH264Frame});
}

View File

@ -1,9 +1,10 @@
import 'dart:async';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
class TalkDataRepository {
TalkDataRepository._() {
_talkDataStreamController = StreamController<TalkData>.broadcast(
_talkDataStreamController = StreamController<TalkDataModel>.broadcast(
onListen: () {
_isListening = true;
},
@ -18,13 +19,13 @@ class TalkDataRepository {
static TalkDataRepository get instance => _instance;
late final StreamController<TalkData> _talkDataStreamController;
late final StreamController<TalkDataModel> _talkDataStreamController;
bool _isListening = false;
//
Stream<TalkData> get talkDataStream => _talkDataStreamController.stream;
Stream<TalkDataModel> get talkDataStream => _talkDataStreamController.stream;
void addTalkData(TalkData talkData) {
void addTalkData(TalkDataModel talkData) {
if (_isListening) {
_talkDataStreamController.add(talkData);
}

View File

@ -15,6 +15,7 @@ import 'package:star_lock/talk/starChart/constant/payload_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/udp_constant.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.dart';
@ -55,10 +56,10 @@ class ScpMessageBaseHandle {
//
final H264FrameHandler frameHandler =
H264FrameHandler(onCompleteFrame: (frameData) {
H264FrameHandler(onCompleteFrame: (TalkDataModel talkDataModel) {
//
TalkDataRepository.instance.addTalkData(
TalkData(contentType: TalkData_ContentTypeE.H264, content: frameData),
talkDataModel,
);
});
@ -71,6 +72,7 @@ class ScpMessageBaseHandle {
messageId: scpMessage.MessageId!,
);
}
//
void replyErrorMessage(ScpMessage scpMessage) {
startChartManage.sendGenericRespErrorMessage(

View File

@ -19,12 +19,14 @@ class VideoTypeE extends $pb.ProtobufEnum {
static const VideoTypeE H264 = VideoTypeE._(1, _omitEnumNames ? '' : 'H264');
static const VideoTypeE IMAGE = VideoTypeE._(2, _omitEnumNames ? '' : 'IMAGE');
static const VideoTypeE VP8 = VideoTypeE._(3, _omitEnumNames ? '' : 'VP8');
static const VideoTypeE H264_720P = VideoTypeE._(4, _omitEnumNames ? '' : 'H264_720P');
static const $core.List<VideoTypeE> values = <VideoTypeE> [
NONE_V,
H264,
IMAGE,
VP8,
H264_720P,
];
static final $core.Map<$core.int, VideoTypeE> _byValue = $pb.ProtobufEnum.initByValue(values);

View File

@ -45,6 +45,7 @@ import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pbserver.dart';
import 'package:star_lock/talk/starChart/status/star_chart_talk_status.dart';
import 'package:star_lock/tools/baseGetXController.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/deviceInfo_utils.dart';
import 'package:star_lock/tools/storage.dart';
import 'package:uuid/uuid.dart';
@ -112,8 +113,10 @@ class StartChartManage {
RbcuConfirm? rbcuConfirm;
final int _maxPayloadSize = 8 * 1024; //
int rotateAngle = 0; //
//
TalkExpectReq _defaultTalkExpect = TalkConstant.ImageExpect;
TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect;
String relayPeerId = ''; // peerId
@ -227,20 +230,6 @@ class StartChartManage {
///
_onReceiveData(_udpSocket!, Get.context!);
// //ToDo:
// //
// Timer.periodic(Duration(seconds: 1), (Timer t) {
// UdpTalkDataHandler().resetDataRates();
// //
// Provider.of<DebugInfoModel>(Get.context!, listen: false)
// .updateDebugInfo(
// UdpTalkDataHandler().getLastRecvDataRate() ~/ 1024, // KB
// UdpTalkDataHandler().getLastRecvPacketCount(),
// UdpTalkDataHandler().getLastSendDataRate() ~/ 1024, // KB
// UdpTalkDataHandler().getLastSendPacketCount(),
// );
// });
}).catchError((error) {
_log(text: 'Failed to bind UDP socket: $error');
});
@ -419,17 +408,36 @@ class StartChartManage {
///
void startCallRequestMessageTimer({required String ToPeerId}) async {
//
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
// h264则跳转至webview
if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) {
Get.toNamed(
Routers.h264WebView,
);
} else {
Get.toNamed(
Routers.starChartTalkView,
);
}
// if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
// // h264则跳转至webview
// if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) {
// Get.toNamed(
// Routers.h264WebView,
// );
// } else {
// Get.toNamed(
// Routers.starChartTalkView,
// );
// }
// }
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
final isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
// 使H264MJPEG
if (isH264) {
Get.toNamed(
Routers.h264WebView,
);
} else if (isMJpeg) {
Get.toNamed(
Routers.starChartTalkView,
);
} else {
Get.toNamed(
Routers.starChartTalkView,
);
}
//
talkRequestTimer ??= Timer.periodic(
@ -1145,7 +1153,7 @@ class StartChartManage {
}
void reSetDefaultTalkExpect() {
_defaultTalkExpect = TalkConstant.ImageExpect;
_defaultTalkExpect = TalkConstant.H264Expect;
}
TalkExpectReq getDefaultTalkExpect() {
@ -1163,12 +1171,27 @@ class StartChartManage {
}
///
void sendImageVideoAndG711AudioTalkExpectData() {
final talkExpectReq = TalkConstant.ImageExpect;
void sendOnlyH264VideoTalkExpectData() {
final talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.H264],
audioType: [],
);
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq);
}
///
void sendImageVideoAndG711AudioTalkExpectData() {
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: TalkConstant.ImageExpect);
}
///
void sendH264VideoAndG711AudioTalkExpectData() {
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: TalkConstant.H264Expect);
}
///
void sendRemoteUnLockMessage({
required String bluetoothDeviceName,
@ -1198,6 +1221,16 @@ class StartChartManage {
///
void destruction() async {
//
final status = talkStatus.status;
if (status == TalkStatus.passiveCallWaitingAnswer ||
status == TalkStatus.proactivelyCallWaitingAnswer ||
status == TalkStatus.answeredSuccessfully ||
status == TalkStatus.uninitialized) {
startTalkRejectMessageTimer();
startTalkHangupMessageTimer();
await Future.delayed(Duration(seconds: 1));
}
isOnlineStarChartServer = false;
//
stopHeartbeat();
@ -1225,7 +1258,6 @@ class StartChartManage {
await Storage.removerStarChartRegisterNodeInfo();
// udp服务
closeUdpSocket();
PacketLossStatistics().reset();
}
///

View File

@ -30,14 +30,13 @@ class AppLifecycleObserver extends WidgetsBindingObserver {
//
final status = StartChartManage().talkStatus.status;
if (status == TalkStatus.passiveCallWaitingAnswer ||
status == TalkStatus.proactivelyCallWaitingAnswer ||
status == TalkStatus.answeredSuccessfully ||
status == TalkStatus.uninitialized) {
StartChartManage().destruction();
Get.back();
}
StartChartManage().destruction();
}
void onAppResumed() async {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,476 @@
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/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 TalkViewNativeDecodePage extends StatefulWidget {
const TalkViewNativeDecodePage({Key? key}) : super(key: key);
@override
State<TalkViewNativeDecodePage> createState() =>
_TalkViewNativeDecodePageState();
}
class _TalkViewNativeDecodePageState extends State<TalkViewNativeDecodePage>
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: SizedBox(
width: 1.sw,
height: 1.sh,
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); //
return state.isLoading.isTrue
? Image.asset(
'images/main/monitorBg.png',
width: screenWidth,
height: screenHeight,
fit: BoxFit.cover,
)
: Positioned.fill(
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: Texture(
textureId: state.textureId.value!,
filterQuality: FilterQuality.medium,
),
)
: Texture(
textureId: state.textureId.value!,
filterQuality: FilterQuality.medium,
),
),
),
),
),
);
},
),
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'),
),
),
),
]);
}
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();
}
}

View File

@ -0,0 +1,120 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/get_rx.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/state_manager.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/starChart/status/star_chart_talk_status.dart';
import 'package:video_decode_plugin/video_decode_plugin.dart';
import '../../../../tools/storage.dart';
enum NetworkStatus {
normal, // 0
lagging, // 1
delayed, // 2
packetLoss // 3
}
class TalkViewNativeDecodeState {
//
static const int maxSourceFps = 25; // 25fps
int udpSendDataFrameNumber = 0; //
// var isSenderAudioData = false.obs;//
Future<String?> userMobileIP = NetworkInfo().getWifiIP();
Future<String?> userUid = Storage.getUid();
RxInt udpStatus =
0.obs; //0 1 2 3 4 5 6 8 9
TextEditingController passwordTF = TextEditingController();
RxList<int> listAudioData = <int>[].obs; //
GlobalKey globalKey = GlobalKey();
Timer? oneMinuteTimeTimer; // 60
RxInt oneMinuteTime = 0.obs; //
// 10
late Timer answerTimer;
late Timer hangUpTimer;
late Timer openDoorTimer;
Timer? fpsTimer;
late AnimationController animationController;
RxInt elapsedSeconds = 0.obs;
//
List<TalkData> audioBuffer = <TalkData>[].obs;
RxBool isLoading = true.obs; //
RxBool isPlaying = false.obs; //
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //
// startChartTalkStatus
final StartChartTalkStatus startChartTalkStatus =
StartChartTalkStatus.instance;
//
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
RxBool isOpenVoice = true.obs; //
RxBool isRecordingScreen = false.obs; //
RxBool isRecordingAudio = false.obs; //
Rx<DateTime> startRecordingAudioTime = DateTime.now().obs; //
Rx<DateTime> endRecordingAudioTime = DateTime.now().obs; //
RxInt recordingAudioTime = 0.obs; //
late VoiceProcessor? voiceProcessor; //
final int frameLength = 320; //640
final int sampleRate = 8000; //8000
RxBool isLongPressing = false.obs; //
// ID
Rx<int?> textureId = Rx<int?>(null);
// FPS监测相关变量
RxInt lastFpsUpdateTime = 0.obs; // FPS更新时间
RxBool showFps = true.obs; // FPS
//
RxDouble decoderFps = 0.0.obs; //
RxDouble messageLossRate = 0.0.obs; //
RxDouble packetLossRate = 0.0.obs; //
RxInt lastPacketStatsUpdateTime = 0.obs; //
//
RxInt renderedFrameCount = 0.obs; //
RxInt totalFrames = 0.obs; //
RxInt droppedFrames = 0.obs; //
RxBool hasSentIDR = false.obs; // IDR帧
RxBool hasSentSPS = false.obs; // SPS
RxBool hasSentPPS = false.obs; // PPS
RxInt keyFrameInterval = 0.obs; // ms
RxInt decodingJitterMs = 0.obs; // ms
//
int lastPerformanceCheck = 0;
int lastFrameCount = 0;
// Mapkey为textureId_frameSeq
Map<String, Map<String, dynamic>> frameTracker = {};
// H264帧缓冲区相关
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区
final int maxFrameBufferSize = 7; //
final int targetFps = 30; // ,native的缓冲区
Timer? frameProcessTimer; //
bool isProcessingFrame = false; //
int lastProcessedTimestamp = 0; //
// H264文件保存相关
String? h264FilePath;
File? h264File;
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'dart:math'; // Import the math package to use sqrt
import 'dart:ui' show decodeImageFromList;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
@ -23,6 +24,7 @@ import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/g711.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
@ -37,32 +39,24 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
final int minBufferSize = 2; // 2166ms
final int maxBufferSize = 20; // 8666ms
int bufferSize = 8; //
//
final int minAudioBufferSize = 1; // 1
final int maxAudioBufferSize = 3; // 3
int audioBufferSize = 2; // 2
int bufferSize = 8; //
//
int _startTime = 0; //
int _startAudioTime = 0; //
bool _isFirstFrame = true; //
int audioBufferSize = 2; // 2
bool _isFirstAudioFrame = true; //
int _startAudioTime = 0; //
//
final List<int> _bufferedAudioFrames = <int>[];
final Map<String, ui.Image> _imageCache = {};
//
bool _isListening = false;
StreamSubscription? _streamSubscription;
//
int _lastFrameTimestamp = 0; // 0
Timer? videoRenderTimer; //
//
int _frameCount = 0;
int _lastFpsUpdateTime = 0;
Timer? _fpsTimer;
int _renderedFrameCount = 0;
int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch;
///
void _initFlutterPcmSound() {
@ -96,18 +90,28 @@ class TalkViewLogic extends BaseGetXController {
//
void _startListenTalkData() {
state.talkDataRepository.talkDataStream.listen((TalkData talkData) async {
final contentType = talkData.contentType;
//
if (_isListening) {
AppLog.log("已经存在数据流监听,避免重复监听");
return;
}
AppLog.log("==== 启动新的数据流监听 ====");
_isListening = true;
_streamSubscription = state.talkDataRepository.talkDataStream
.listen((TalkDataModel talkDataModel) async {
final talkData = talkDataModel.talkData;
final contentType = talkData!.contentType;
final currentTime = DateTime.now().millisecondsSinceEpoch;
//
switch (contentType) {
case TalkData_ContentTypeE.G711:
// //
// if (_isFirstAudioFrame) {
// _startAudioTime = currentTime;
// _isFirstAudioFrame = false;
// }
if (_isFirstAudioFrame) {
_startAudioTime = currentTime;
_isFirstAudioFrame = false;
}
//
final expectedTime = _startAudioTime + talkData.durationMs;
@ -129,80 +133,16 @@ class TalkViewLogic extends BaseGetXController {
_playAudioFrames();
break;
case TalkData_ContentTypeE.Image:
//
if (_isFirstFrame) {
_startTime = currentTime;
_isFirstFrame = false;
AppLog.log('第一帧帧的时间戳:${talkData.durationMs}');
}
// AppLog.log('其他帧的时间戳:${talkData.durationMs}');
//
if (_lastFrameTimestamp != 0) {
final int frameInterval = talkData.durationMs - _lastFrameTimestamp;
_adjustBufferSize(frameInterval); //
}
_lastFrameTimestamp = talkData.durationMs; //
//
if (state.videoBuffer.length >= bufferSize) {
state.videoBuffer.removeAt(0);
}
// bufferSize帧
state.videoBuffer.add(talkData);
//
await _decodeAndCacheFrame(talkData);
//
_playVideoFrames();
if (state.videoBuffer.length > bufferSize) {
state.videoBuffer.removeAt(0); //
}
break;
}
});
}
//
void _playVideoFrames() {
//
if (state.videoBuffer.isEmpty || state.videoBuffer.length < bufferSize) {
// AppLog.log('📊 缓冲中 - 当前缓冲区大小: ${state.videoBuffer.length}/${bufferSize}');
return;
}
//
TalkData? oldestFrame;
int oldestIndex = -1;
for (int i = 0; i < state.videoBuffer.length; i++) {
if (oldestFrame == null ||
state.videoBuffer[i].durationMs < oldestFrame.durationMs) {
oldestFrame = state.videoBuffer[i];
oldestIndex = i;
}
}
//
if (oldestFrame != null && oldestIndex != -1) {
final cacheKey = oldestFrame.content.hashCode.toString();
// 使
if (_imageCache.containsKey(cacheKey)) {
state.currentImage.value = _imageCache[cacheKey];
state.listData.value = Uint8List.fromList(oldestFrame.content);
state.videoBuffer.removeAt(oldestIndex); //
// //
// _frameCount++;
// final currentTime = DateTime.now().millisecondsSinceEpoch;
// final elapsed = currentTime - _lastFpsUpdateTime;
//
// if (elapsed >= 1000) {
// //
// state.fps.value = (_frameCount * 1000 / elapsed).round();
// _frameCount = 0;
// _lastFpsUpdateTime = currentTime;
// }
} else {
// AppLog.log('⚠️ 帧未找到缓存 - Key: $cacheKey');
state.videoBuffer.removeAt(oldestIndex); //
}
}
}
//
void _playAudioFrames() {
//
@ -233,50 +173,6 @@ class TalkViewLogic extends BaseGetXController {
}
}
//
Future<void> _decodeAndCacheFrame(TalkData talkData) async {
try {
String cacheKey = talkData.content.hashCode.toString();
//
if (!_imageCache.containsKey(cacheKey)) {
final Uint8List uint8Data = Uint8List.fromList(talkData.content);
final ui.Image image = await decodeImageFromList(uint8Data);
//
if (_imageCache.length >= bufferSize) {
_imageCache.remove(_imageCache.keys.first);
}
//
_imageCache[cacheKey] = image;
// AppLog.log('📥 缓存新帧 - 缓存数: ${_imageCache.length}, Key: $cacheKey');
}
} catch (e) {
AppLog.log('❌ 帧解码错误: $e');
}
}
//
void _adjustBufferSize(int frameInterval) {
const int frameDuration = 83; // 83ms12fps
const int delayThresholdHigh = frameDuration * 2; // 2
const int delayThresholdLow = frameDuration; // 1
const int adjustInterval = 1; // 1
if (frameInterval > delayThresholdHigh && bufferSize < maxBufferSize) {
//
bufferSize = min(bufferSize + adjustInterval, maxBufferSize);
AppLog.log('📈 增加缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms');
} else if (frameInterval < delayThresholdLow &&
bufferSize > minBufferSize) {
//
bufferSize = max(bufferSize - adjustInterval, minBufferSize);
AppLog.log('📉 减少缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms');
}
}
///
void _startListenTalkStatus() {
state.startChartTalkStatus.statusStream.listen((talkStatus) {
@ -382,20 +278,6 @@ class TalkViewLogic extends BaseGetXController {
}
}
// token
Future<void> _getLockNetToken() async {
final LockNetTokenEntity entity = await ApiRepository.to.getLockNetToken(
lockId: lockDetailState.keyInfos.value.lockId.toString());
if (entity.errorCode!.codeIsSuccessful) {
lockDetailState.lockNetToken = entity.data!.token!.toString();
AppLog.log('从服务器获取联网token:${lockDetailState.lockNetToken}');
} else {
BuglyTool.uploadException(
message: '点击了需要联网开锁', detail: '点击了需要联网开锁 获取连网token失败', upload: true);
showToast('网络访问失败,请检查网络是否正常'.tr, something: () {});
}
}
///
Future<bool> getPermissionStatus() async {
final Permission permission = Permission.microphone;
@ -498,6 +380,31 @@ class TalkViewLogic extends BaseGetXController {
requestPermissions();
// 10fps
videoRenderTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
final int now = DateTime.now().millisecondsSinceEpoch;
if (state.videoBuffer.isNotEmpty) {
final TalkData oldestFrame = state.videoBuffer.removeAt(0);
if (oldestFrame.content.isNotEmpty) {
state.listData.value = Uint8List.fromList(oldestFrame.content); //
final int decodeStart = DateTime.now().millisecondsSinceEpoch;
decodeImageFromList(Uint8List.fromList(oldestFrame.content)).then((ui.Image img) {
final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
state.currentImage.value = img;
_renderedFrameCount++;
// fps
if (now - _lastFpsPrintTime >= 1000) {
// print('实际渲染fps: $_renderedFrameCount');
_renderedFrameCount = 0;
_lastFpsPrintTime = now;
}
}).catchError((e) {
print('图片解码失败: $e');
});
}
}
//
});
}
@override
@ -512,10 +419,17 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio();
//
_imageCache.clear();
// _imageCache.clear();
state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer = null; //
state.oneMinuteTime.value = 0;
//
_streamSubscription?.cancel();
_isListening = false;
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.onClose();
}
@ -525,6 +439,9 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio();
//
StartChartManage().reSetDefaultTalkExpect();
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.dispose();
}

View File

@ -12,6 +12,7 @@ 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/talkView/talk_view_logic.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
@ -30,6 +31,7 @@ class _TalkViewPageState extends State<TalkViewPage>
final TalkViewLogic logic = Get.put(TalkViewLogic());
final TalkViewState state = Get.find<TalkViewLogic>().state;
late Stream<int> _latencyStream;
final startChartManage = StartChartManage();
@override
void initState() {
@ -132,17 +134,13 @@ class _TalkViewPageState extends State<TalkViewPage>
key: state.globalKey,
child: SizedBox.expand(
child: RotatedBox(
quarterTurns: -1,
child: Obx(
() => state.currentImage.value != null
? RawImage(
image: state.currentImage.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
)
: Container(color: Colors.transparent),
quarterTurns: startChartManage.rotateAngle ~/ 90,
child: RawImage(
image: state.currentImage.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
),
),
),
@ -158,35 +156,33 @@ class _TalkViewPageState extends State<TalkViewPage>
style: TextStyle(color: Colors.black, fontSize: 26.sp),
))
: Container()),
Obx(
() => state.listData.value.isNotEmpty &&
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(),
),
Obx(() =>
state.listData.value.isNotEmpty && 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(
@ -456,7 +452,6 @@ class _TalkViewPageState extends State<TalkViewPage>
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully &&
// state.listData.value.length > 0) {
// logic.udpOpenDoorAction();
logic.remoteOpenLock();
// }
// if (UDPManage().remoteUnlock == 1) {
// logic.udpOpenDoorAction();
@ -464,6 +459,7 @@ class _TalkViewPageState extends State<TalkViewPage>
// } else {
// logic.showToast('请在锁设置中开启远程开锁'.tr);
// }
logic.remoteOpenLock();
},
)
]);

View File

@ -90,6 +90,5 @@ class TalkViewState {
RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
//
final Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
}

View File

@ -25,7 +25,9 @@ import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/g711.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
@ -44,14 +46,20 @@ class H264WebViewLogic extends BaseGetXController {
//
static const int CHUNK_SIZE = 4096;
Timer? _mockDataTimer;
int _startAudioTime = 0; //
int audioBufferSize = 2; // 2
bool _isFirstAudioFrame = true; //
//
final List<int> _bufferedAudioFrames = <int>[];
final Queue<List<int>> _frameBuffer = Queue<List<int>>();
static const int FRAME_BUFFER_SIZE = 25;
//
bool _isListening = false;
StreamSubscription? _streamSubscription;
@override
void onInit() {
super.onInit();
// WebView
state.webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
@ -63,18 +71,40 @@ class H264WebViewLogic extends BaseGetXController {
},
);
state.isShowLoading.value = true;
// HTML
_loadLocalHtml();
super.onInit();
//
_createFramesStreamListen();
// playLocalTestVideo();
_startListenTalkStatus();
state.talkStatus.value = state.startChartTalkStatus.status;
//
_initFlutterPcmSound();
//
_initAudioRecorder();
// HTML
_loadLocalHtml();
// playLocalTestVideo();
requestPermissions();
}
Future<void> requestPermissions() async {
//
var storageStatus = await Permission.storage.request();
//
var microphoneStatus = await Permission.microphone.request();
if (storageStatus.isGranted && microphoneStatus.isGranted) {
print("Permissions granted");
} else {
print("Permissions denied");
//
if (await Permission.storage.isPermanentlyDenied) {
openAppSettings(); //
}
}
}
///
@ -96,16 +126,66 @@ class H264WebViewLogic extends BaseGetXController {
}
void _createFramesStreamListen() async {
state.talkDataRepository.talkDataStream.listen((TalkData event) async {
//
_frameBuffer.add(event.content);
//
if (_isListening) {
AppLog.log("已经存在数据流监听,避免重复监听");
return;
}
// ,
while (_frameBuffer.length > FRAME_BUFFER_SIZE) {
if (_frameBuffer.isNotEmpty) {
final frame = _frameBuffer.removeFirst();
await _sendBufferedData(frame);
}
AppLog.log("==== 启动新的数据流监听 ====");
_isListening = true;
_streamSubscription = state.talkDataRepository.talkDataStream
.listen((TalkDataModel talkDataModel) async {
final talkData = talkDataModel.talkData;
final contentType = talkData!.contentType;
final currentTime = DateTime.now().millisecondsSinceEpoch;
//
switch (contentType) {
case TalkData_ContentTypeE.G711:
if (state.isShowLoading.isFalse) {
// //
if (_isFirstAudioFrame) {
_startAudioTime = currentTime;
_isFirstAudioFrame = false;
}
//
final expectedTime = _startAudioTime + talkData.durationMs;
final audioDelay = currentTime - expectedTime;
//
if (audioDelay > 500) {
state.audioBuffer.clear();
if (state.isOpenVoice.value) {
_playAudioFrames();
}
return;
}
if (state.audioBuffer.length >= audioBufferSize) {
state.audioBuffer.removeAt(0); //
}
state.audioBuffer.add(talkData); //
//
_playAudioFrames();
}
break;
case TalkData_ContentTypeE.H264:
// //
_frameBuffer.add(talkData.content);
// ,
while (_frameBuffer.length > FRAME_BUFFER_SIZE) {
if (_frameBuffer.isNotEmpty) {
final frame = _frameBuffer.removeFirst();
await _sendBufferedData(frame);
}
if (state.isShowLoading.isTrue) {
state.isShowLoading.value = false;
}
}
break;
}
});
}
@ -134,6 +214,51 @@ class H264WebViewLogic extends BaseGetXController {
// }
// }
//
void _playAudioFrames() {
//
//
if (state.audioBuffer.isEmpty ||
state.audioBuffer.length < audioBufferSize) {
return;
}
//
TalkData? oldestFrame;
int oldestIndex = -1;
for (int i = 0; i < state.audioBuffer.length; i++) {
if (oldestFrame == null ||
state.audioBuffer[i].durationMs < oldestFrame.durationMs) {
oldestFrame = state.audioBuffer[i];
oldestIndex = i;
}
}
//
if (oldestFrame != null && oldestIndex != -1) {
if (state.isOpenVoice.value) {
//
_playAudioData(oldestFrame);
}
state.audioBuffer.removeAt(oldestIndex);
}
}
///
void _playAudioData(TalkData talkData) async {
if (state.isOpenVoice.value) {
final list =
G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150);
// // PCM PcmArrayInt16
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
FlutterPcmSound.feed(fromList);
if (!state.isPlaying.value) {
FlutterPcmSound.play();
state.isPlaying.value = true;
}
}
}
/// html文件
Future<void> _loadLocalHtml() async {
// HTML
@ -186,10 +311,10 @@ class H264WebViewLogic extends BaseGetXController {
Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (state.isShowLoading.isFalse) {
state.oneMinuteTime.value++;
if (state.oneMinuteTime.value >= 60) {
t.cancel(); //
state.oneMinuteTime.value = 0;
}
// if (state.oneMinuteTime.value >= 60) {
// t.cancel(); //
// state.oneMinuteTime.value = 0;
// }
}
});
break;
@ -321,7 +446,7 @@ class H264WebViewLogic extends BaseGetXController {
}
//
List<int> amplifiedFrame = _applyGain(frame, 1.6);
List<int> amplifiedFrame = _applyGain(frame, 1.8);
// G711数据
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData);
@ -409,7 +534,7 @@ class H264WebViewLogic extends BaseGetXController {
});
final LockSetInfoEntity lockSetInfoEntity =
await ApiRepository.to.getLockSettingInfoData(
await ApiRepository.to.getLockSettingInfoData(
lockId: lockId.toString(),
);
if (lockSetInfoEntity.errorCode!.codeIsSuccessful) {
@ -427,6 +552,38 @@ class H264WebViewLogic extends BaseGetXController {
}
}
///
void _stopPlayG711Data() async {
await FlutterPcmSound.pause();
await FlutterPcmSound.stop();
await FlutterPcmSound.clear();
}
@override
void onClose() {
_stopPlayG711Data(); //
state.audioBuffer.clear(); //
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
//
stopProcessingAudio();
state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer = null; //
state.oneMinuteTime.value = 0;
//
_streamSubscription?.cancel();
_isListening = false;
//
StartChartManage().reSetDefaultTalkExpect();
super.onClose();
}
@override
void dispose() {

View File

@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_colors.dart';
import 'package:star_lock/app_settings/app_settings.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/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart';
@ -25,6 +26,7 @@ class _H264WebViewState extends State<H264WebView>
with TickerProviderStateMixin {
final H264WebViewLogic logic = Get.put(H264WebViewLogic());
final H264WebViewState state = Get.find<H264WebViewLogic>().state;
final startChartManage = StartChartManage();
@override
void initState() {
@ -71,8 +73,13 @@ class _H264WebViewState extends State<H264WebView>
height: screenHeight,
fit: BoxFit.cover,
)
: WebViewWidget(
controller: state.webViewController,
: SizedBox.expand(
child: RotatedBox(
quarterTurns: startChartManage.rotateAngle ~/ 90,
child: WebViewWidget(
controller: state.webViewController,
),
),
);
}),
Obx(
@ -411,10 +418,13 @@ class _H264WebViewState extends State<H264WebView>
),
);
}
@override
void dispose() {
state.animationController.dispose(); //
super.dispose();
state.animationController.dispose();
CallTalk().finishAVData();
// UdpTalkDataHandler().resetDataRates();
super.dispose();
}
}

View File

@ -5,6 +5,7 @@ import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:get/get.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/starChart/status/star_chart_talk_status.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
import 'package:webview_flutter/webview_flutter.dart';
@ -49,4 +50,6 @@ class H264WebViewState {
RxInt rotateAngle = 0.obs; //
RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
List<TalkData> audioBuffer = <TalkData>[].obs;
RxBool isPlaying = false.obs; //
}

View File

@ -127,7 +127,10 @@ dependencies:
sdk: flutter
aliyun_face_plugin:
path: aliyun_face_plugin
video_decode_plugin:
git:
url: git@code.star-lock.cn:liyi/video_decode_plugin.git
ref: 38df1883f5108ec1ce590ba52318815333fded38
flutter_localizations:
sdk: flutter
@ -275,6 +278,8 @@ dependencies:
provider: ^6.1.2
dio: ^4.0.6 # 网络请求库
video_thumbnail: ^0.5.3
# 角标管理
flutter_app_badger: ^1.3.0