Merge branch 'develop_sky' into 'master_sky'

Develop sky

See merge request StarlockTeam/app-starlock!111
This commit is contained in:
李仪 2025-05-30 06:20:18 +00:00
commit c30dfdc1eb
66 changed files with 4919 additions and 891 deletions

View File

@ -16,10 +16,10 @@ variables:
- macos - macos
- flutter - flutter
rules: rules:
- if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release" - if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/ - if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
- if: $CI_COMMIT_BRANCH == "canary_release" - if: $CI_COMMIT_BRANCH == "canary_release_sky"
- if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$/ - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$/
.notify_rule: .notify_rule:
@ -27,8 +27,8 @@ variables:
- macos - macos
- flutter - flutter
rules: rules:
- if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release" - if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/ - if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
.generate_tag_rule: .generate_tag_rule:
@ -36,16 +36,16 @@ variables:
- macos - macos
- flutter - flutter
rules: rules:
- if: $CI_COMMIT_BRANCH == "master" - if: $CI_COMMIT_BRANCH == "master_sky"
.generate_next_version_rule: .generate_next_version_rule:
tags: tags:
- macos - macos
- flutter - flutter
rules: rules:
- if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release" - if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH == "canary_release" - if: $CI_COMMIT_BRANCH == "canary_release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/ - if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
.print_env: .print_env:

View File

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

View File

@ -8,8 +8,9 @@ export ENV_BUILD_WORKSPACE=${CI_PROJECT_DIR}
echo "GITLAB_WORKSPACE: ${CI_PROJECT_DIR}" echo "GITLAB_WORKSPACE: ${CI_PROJECT_DIR}"
cd ${CI_PROJECT_DIR}/android cd ${CI_PROJECT_DIR}/android
echo "ENV_BUILD_TAG:${ENV_BUILD_TAG},ENV_BUILD_BRANCH:${ENV_BUILD_BRANCH}" echo "ENV_BUILD_TAG:${ENV_BUILD_TAG},ENV_BUILD_BRANCH:${ENV_BUILD_BRANCH}"
regex='^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$' # 只支持 v1.2.3_sky 这种tag格式
if [[ "${ENV_BUILD_BRANCH}" == "canary_release" ]]; then regex='^v[0-9]+\.[0-9]+\.[0-9]+_sky$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release_sky" ]]; then
echo "===build canary_release: ${NEXT_VERSION}" echo "===build canary_release: ${NEXT_VERSION}"
export ENV_BUILD_TAG=${NEXT_VERSION} export ENV_BUILD_TAG=${NEXT_VERSION}
bundle exec fastlane release_apk flavor:xhj --verbose bundle exec fastlane release_apk flavor:xhj --verbose
@ -20,11 +21,11 @@ elif [[ $ENV_BUILD_TAG =~ $regex ]]; then
bundle exec fastlane release_apk flavor:sky --verbose bundle exec fastlane release_apk flavor:sky --verbose
bundle exec fastlane release_bundle flavor:xhj_bundle --verbose bundle exec fastlane release_bundle flavor:xhj_bundle --verbose
bundle exec fastlane release_bundle flavor:sky --verbose bundle exec fastlane release_bundle flavor:sky --verbose
elif [[ "${ENV_BUILD_BRANCH}" == "develop" ]]; then elif [[ "${ENV_BUILD_BRANCH}" == "develop_sky" ]]; then
echo "===build dev===${NEXT_VERSION}" echo "===build dev===${NEXT_VERSION}"
bundle exec fastlane beta flavor:xhj env:dev --verbose bundle exec fastlane beta flavor:xhj env:dev --verbose
bundle exec fastlane beta flavor:sky env:dev --verbose bundle exec fastlane beta flavor:sky env:dev --verbose
elif [[ "${ENV_BUILD_BRANCH}" == "release" ]] || [[ "${ENV_BUILD_BRANCH}" == "feat_devops" ]] ; then elif [[ "${ENV_BUILD_BRANCH}" == "release_sky" || "${ENV_BUILD_BRANCH}" == "feat_devops_sky" ]] ; then
echo "===build pre===${NEXT_VERSION}" echo "===build pre===${NEXT_VERSION}"
bundle exec fastlane beta flavor:xhj env:pre --verbose bundle exec fastlane beta flavor:xhj env:pre --verbose
bundle exec fastlane beta flavor:sky env:pre --verbose bundle exec fastlane beta flavor:sky env:pre --verbose

View File

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

View File

@ -9,8 +9,9 @@ echo "GITLAB_WORKSPACE: ${CI_PROJECT_DIR}"
cd ${CI_PROJECT_DIR}/ios cd ${CI_PROJECT_DIR}/ios
#bundle exec pod install #bundle exec pod install
echo "ENV_BUILD_TAG:${ENV_BUILD_TAG},ENV_BUILD_BRANCH:${ENV_BUILD_BRANCH}" echo "ENV_BUILD_TAG:${ENV_BUILD_TAG},ENV_BUILD_BRANCH:${ENV_BUILD_BRANCH}"
regex='^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$' # 只支持 v1.2.3_sky 这种tag格式
if [[ "${ENV_BUILD_BRANCH}" == "canary_release" ]]; then regex='^v[0-9]+\.[0-9]+\.[0-9]+_sky$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release_sky" ]]; then
echo "===build canary_release: ${NEXT_VERSION}" echo "===build canary_release: ${NEXT_VERSION}"
export ENV_BUILD_TAG=${NEXT_VERSION} export ENV_BUILD_TAG=${NEXT_VERSION}
bundle exec fastlane release_ipa flavor:xhj --verbose bundle exec fastlane release_ipa flavor:xhj --verbose
@ -19,11 +20,11 @@ elif [[ $ENV_BUILD_TAG =~ $regex ]]; then
echo "===build release===$ENV_BUILD_TAG" echo "===build release===$ENV_BUILD_TAG"
bundle exec fastlane release_ipa flavor:xhj --verbose bundle exec fastlane release_ipa flavor:xhj --verbose
bundle exec fastlane release_ipa flavor:sky --verbose bundle exec fastlane release_ipa flavor:sky --verbose
elif [[ "${ENV_BUILD_BRANCH}" == "develop" ]]; then elif [[ "${ENV_BUILD_BRANCH}" == "develop_sky" ]]; then
echo "===build dev===${NEXT_VERSION}" echo "===build dev===${NEXT_VERSION}"
bundle exec fastlane beta flavor:xhj env:Dev --verbose bundle exec fastlane beta flavor:xhj env:Dev --verbose
bundle exec fastlane beta flavor:sky env:Dev --verbose bundle exec fastlane beta flavor:sky env:Dev --verbose
elif [[ "${ENV_BUILD_BRANCH}" == "release" ]] || [[ "${ENV_BUILD_BRANCH}" == "feat_devops" ]] ; then elif [[ "${ENV_BUILD_BRANCH}" == "release_sky" || "${ENV_BUILD_BRANCH}" == "feat_devops_sky" ]] ; then
echo "===build pre===${NEXT_VERSION}" echo "===build pre===${NEXT_VERSION}"
bundle exec fastlane beta flavor:xhj env:Pre --verbose bundle exec fastlane beta flavor:xhj env:Pre --verbose
bundle exec fastlane beta flavor:sky env:Pre --verbose bundle exec fastlane beta flavor:sky env:Pre --verbose

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/advancedFeaturesWeb/advancedFeaturesWeb_page.dart';
import 'package:star_lock/mine/valueAddedServices/advancedFunctionRecord/advancedFunctionRecord_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/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/views/talkView/talk_view_page.dart';
import 'package:star_lock/talk/starChart/webView/h264_web_view.dart'; import 'package:star_lock/talk/starChart/webView/h264_web_view.dart';
@ -202,6 +203,7 @@ import 'mine/valueAddedServices/valueAddedServicesRealName/value_added_services_
import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesAddSMSTemplate/newSMSTemplate_page.dart'; import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesAddSMSTemplate/newSMSTemplate_page.dart';
import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesListSMSTemplate/customSMSTemplateList_page.dart'; import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesListSMSTemplate/customSMSTemplateList_page.dart';
import 'starLockApplication/starLockApplication.dart'; import 'starLockApplication/starLockApplication.dart';
import 'talk/starChart/views/imageTransmission/image_transmission_page.dart';
import 'tools/seletKeyCyclicDate/seletKeyCyclicDate_page.dart'; import 'tools/seletKeyCyclicDate/seletKeyCyclicDate_page.dart';
abstract class Routers { abstract class Routers {
@ -514,6 +516,8 @@ abstract class Routers {
static const String starChartPage = '/starChartPage'; // static const String starChartPage = '/starChartPage'; //
static const String starChartTalkView = '/starChartTalkView'; // static const String starChartTalkView = '/starChartTalkView'; //
static const String h264WebView = '/h264WebView'; // static const String h264WebView = '/h264WebView'; //
static const String imageTransmissionView =
'/imageTransmissionView'; //()
} }
abstract class AppRouters { abstract class AppRouters {
@ -1184,6 +1188,13 @@ abstract class AppRouters {
page: () => const DoubleLockLinkPage()), page: () => const DoubleLockLinkPage()),
GetPage<dynamic>( GetPage<dynamic>(
name: Routers.starChartTalkView, page: () => const TalkViewPage()), name: Routers.starChartTalkView, page: () => const TalkViewPage()),
GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()), GetPage<dynamic>(
name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()),
//
GetPage<dynamic>(
name: Routers.imageTransmissionView,
page: () => ImageTransmissionPage()),
//
// 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:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.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/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/commonDataManage.dart';
@ -190,10 +191,12 @@ class BlueManage {
continue; continue;
} }
final isMatch = _isMatch(scanResult final isMatch = _isMatch(
.advertisementData.serviceUuids scanResult.advertisementData.serviceUuids
.map((e) => e.uuid) .map((e) => e.uuid)
.toList()); .toList(),
isSingle: true,
);
if (isMatch && (scanResult.rssi >= -100)) { if (isMatch && (scanResult.rssi >= -100)) {
// id相同的元素 // id相同的元素
@ -276,6 +279,7 @@ class BlueManage {
.map((e) => e.uuid) .map((e) => e.uuid)
.toList(), .toList(),
deviceType: deviceType, deviceType: deviceType,
isSingle: false,
); );
// //
if (isMatch && (scanResult.rssi >= -100)) { if (isMatch && (scanResult.rssi >= -100)) {
@ -320,26 +324,47 @@ class BlueManage {
/// uuid /// uuid
bool _isMatch(List<String> serviceUuids, bool _isMatch(List<String> serviceUuids,
{DeviceType deviceType = DeviceType.blue}) { {DeviceType deviceType = DeviceType.blue, required bool isSingle}) {
final List<String> prefixes = final List<String> prefixes =
getDeviceType(deviceType).map((e) => e.toLowerCase()).toList(); getDeviceType(deviceType).map((e) => e.toLowerCase()).toList();
for (String uuid in serviceUuids) { for (String uuid in serviceUuids) {
final String cleanUuid = uuid.replaceAll('-', '').toLowerCase(); final String cleanUuid = uuid.toLowerCase();
if (cleanUuid.length == 8) { if (cleanUuid.length == 8) {
// 8 // 845
String pairStatus = cleanUuid.substring(4, 6); // 4534
for (final prefix in prefixes) { for (final prefix in prefixes) {
if (cleanUuid.startsWith(prefix)) { if (cleanUuid.startsWith(prefix)) {
return true; if (isSingle) {
return true; // isSingle为truetrue
} else {
// 00=01=
if (pairStatus == '00') {
return true; // true
}
// 01trueuuid
}
} }
} }
} else if (cleanUuid.length == 32) { } else {
// 128834 // 128834
final String first8 = cleanUuid.substring(0, 8); if (cleanUuid.length >= 32) {
if (first8.length >= 4) { final String thirdAndFourth = cleanUuid.substring(2, 4); // 23
final String thirdAndFourth = first8.substring(2, 4); // 23
for (final prefix in prefixes) { for (final prefix in prefixes) {
if (thirdAndFourth == prefix) { if (thirdAndFourth == prefix) {
return true; if (isSingle) {
return true; // isSingle为truetrue
} else {
// UUID的第31321
if (cleanUuid.length >= 32) {
String pairStatus =
cleanUuid.substring(30, 32); // 31321
// 00=01=
if (pairStatus == '00') {
return true; // true
}
// 01trueuuid
}
}
} }
} }
} }
@ -567,7 +592,10 @@ class BlueManage {
}); });
} else { } else {
connectStateCallBack(BluetoothConnectionState.disconnected); connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds); if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear(); scanDevices.clear();
BuglyTool.uploadException( BuglyTool.uploadException(
@ -598,7 +626,9 @@ class BlueManage {
}); });
} else { } else {
connectStateCallBack(BluetoothConnectionState.disconnected); connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds); if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear(); scanDevices.clear();
BuglyTool.uploadException( BuglyTool.uploadException(
@ -791,7 +821,7 @@ class BlueManage {
} }
} }
// ///
Future<void> writeCharacteristicWithResponse(List<int> value) async { Future<void> writeCharacteristicWithResponse(List<int> value) async {
final List<BluetoothService> services = final List<BluetoothService> services =
await bluetoothConnectDevice!.discoverServices(); await bluetoothConnectDevice!.discoverServices();
@ -801,30 +831,70 @@ class BlueManage {
in service.characteristics) { in service.characteristics) {
if (characteristic.characteristicUuid == _characteristicIdWrite) { if (characteristic.characteristicUuid == _characteristicIdWrite) {
try { try {
//
int retryCount = 0;
const int maxRetries = 3;
const int retryDelayMs = 500;
final List<int> valueList = value; final List<int> valueList = value;
final List subData = splitList(valueList, _mtuSize!); final List subData = splitList(valueList, _mtuSize!);
// AppLog.log('writeCharacteristicWithResponse _mtuSize:$_mtuSize 得到的分割数据:$subData');
for (int i = 0; i < subData.length; i++) { for (int i = 0; i < subData.length; i++) {
if (characteristic.properties.writeWithoutResponse) { //
// 使WRITE_NO_RESPONSE属性写入值 bool packetSent = false;
await characteristic.write(subData[i], withoutResponse: true); retryCount = 0;
} else if (characteristic.properties.write) {
// 使WRITE属性写入值 while (!packetSent && retryCount < maxRetries) {
await characteristic.write(subData[i]); try {
} else { 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( throw Exception(
'This characteristic does not support writing.'); '蓝牙写入失败,数据包 ${i + 1}/${subData.length} 已达到最大重试次数');
} }
} }
return; //
} on Exception catch (e, s) { } on Exception catch (e, s) {
AppLog.log('APP写入失败: $e $s'); AppLog.log('APP写入失败: $e $s');
rethrow; rethrow;
} }
} }
} }
} }
} }
//
throw Exception('未找到适合写入的蓝牙特性');
} }
// //

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.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_logic.dart';
import 'package:star_lock/main/lockDetail/face/faceDetail/faceDetail_state.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, // isHaveRightWidget: true,
// isHaveLine: true, // isHaveLine: true,
// rightWidget: SizedBox(width: 60.w, height: 50.h, child: _isStressFace()))), // rightWidget: SizedBox(width: 60.w, height: 50.h, child: _isStressFace()))),
Obx(() => CommonItem( Visibility(
leftTitel: '是否为管理员'.tr, visible: !F.isSKY,
rightTitle: '', child: Obx(
isTipsImg: false, () => CommonItem(
isHaveRightWidget: true, leftTitel: '是否为管理员'.tr,
rightWidget: rightTitle: '',
SizedBox(width: 60.w, height: 50.h, child: _isAdmin()))), isTipsImg: false,
isHaveRightWidget: true,
rightWidget: SizedBox(
width: 60.w,
height: 50.h,
child: _isAdmin(),
),
),
),
),
Container(height: 10.h), Container(height: 10.h),
CommonItem( CommonItem(
leftTitel: '操作记录'.tr, leftTitel: '操作记录'.tr,

View File

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

View File

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

View File

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

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

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -88,7 +89,6 @@ class _LockDetailPageState extends State<LockDetailPage>
/// ///
AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!); AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);
state.isOpenLockNeedOnline.refresh(); state.isOpenLockNeedOnline.refresh();
} }
StreamSubscription? _lockRefreshLockDetailInfoDataEvent; StreamSubscription? _lockRefreshLockDetailInfoDataEvent;
@ -1103,13 +1103,15 @@ class _LockDetailPageState extends State<LockDetailPage>
})); }));
// //
showWidgetArr.add(bottomItem('images/main/icon_main_password.png', '密码'.tr, if (state.keyInfos.value.lockFeature!.password == 1) {
state.bottomBtnisEable.value, () { showWidgetArr.add(bottomItem('images/main/icon_main_password.png',
Get.toNamed(Routers.passwordKeyListPage, '密码'.tr, state.bottomBtnisEable.value, () {
arguments: <String, LockListInfoItemEntity>{ Get.toNamed(Routers.passwordKeyListPage,
'keyInfo': state.keyInfos.value arguments: <String, LockListInfoItemEntity>{
}); 'keyInfo': state.keyInfos.value
})); });
}));
}
// ic卡 // ic卡
if (state.keyInfos.value.lockFeature!.icCard == 1) { if (state.keyInfos.value.lockFeature!.icCard == 1) {
@ -1179,7 +1181,7 @@ class _LockDetailPageState extends State<LockDetailPage>
} }
//-> //->
if (state.keyInfos.value.lockFeature!.videoIntercom == 1) { if (state.keyInfos.value.lockFeature!.isSupportCatEye == 1) {
showWidgetArr.add( showWidgetArr.add(
bottomItem('images/main/icon_catEyes.png', '监控'.tr, bottomItem('images/main/icon_catEyes.png', '监控'.tr,
state.bottomBtnisEable.value, () async { state.bottomBtnisEable.value, () async {
@ -1467,7 +1469,7 @@ class _LockDetailPageState extends State<LockDetailPage>
state.iSOpenLock.value = true; state.iSOpenLock.value = true;
state.openLockBtnState.value = 1; state.openLockBtnState.value = 1;
state.animationController!.forward(); state.animationController!.forward();
// AppLog.log('点击开锁'); AppLog.log('点击开锁');
if (isOpenLockNeedOnline) { if (isOpenLockNeedOnline) {
// //
state.openDoorModel = 0; 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 '../../../blue/io_reply.dart';
import '../../lockMian/entity/lockListInfo_entity.dart'; import '../../lockMian/entity/lockListInfo_entity.dart';
class LockDetailState { class LockDetailState {
Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs; Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs;
final Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs; final Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
late StreamSubscription<Reply> replySubscription; late StreamSubscription<Reply> replySubscription;
StreamSubscription? lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent; StreamSubscription?
lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent;
StreamSubscription? LockSetChangeSetRefreshLockDetailWithTypeSubscription; StreamSubscription? LockSetChangeSetRefreshLockDetailWithTypeSubscription;
StreamSubscription? DetailLockInfo; StreamSubscription? DetailLockInfo;
StreamSubscription? SuccessfulDistributionNetworkEvent; StreamSubscription? SuccessfulDistributionNetworkEvent;
String lockNetToken = '0'; String lockNetToken = '0';
int differentialTime = 0;// int differentialTime = 0; //
bool isHaveNetwork = true; bool isHaveNetwork = true;
int lockUserNo = 0; int lockUserNo = 0;
int senderUserId = 0; int senderUserId = 0;
@ -41,7 +41,7 @@ class LockDetailState {
RxBool bottomBtnisEable = true.obs; // RxBool bottomBtnisEable = true.obs; //
RxBool openDoorBtnisUneable = true.obs; // 使使, RxBool openDoorBtnisUneable = true.obs; // 使使,
int openDoorModel = 0;// 线0, 线2 线32 线34 int openDoorModel = 0; // 线0, 线2 线32 线34
// //
AnimationController? animationController; AnimationController? animationController;

View File

@ -158,23 +158,23 @@ class _BasicInformationPageState extends State<BasicInformationPage> {
allHeight: 70.h, allHeight: 70.h,
isHaveLine: true), isHaveLine: true),
)), )),
Obx(() => CommonItem( // Obx(() => CommonItem(
leftTitel: '位置信息'.tr, // leftTitel: '位置信息'.tr,
// rightTitle: state.lockBasicInfo.value.address ?? "-", // // rightTitle: state.lockBasicInfo.value.address ?? "-",
allHeight: 80.h, // allHeight: 80.h,
isHaveLine: false, // isHaveLine: false,
isHaveRightWidget: true, // isHaveRightWidget: true,
rightWidget: SizedBox( // rightWidget: SizedBox(
width: 300.w, // width: 300.w,
child: Text(state.lockBasicInfo.value.address ?? ''.tr, // child: Text(state.lockBasicInfo.value.address ?? ''.tr,
maxLines: 2, // maxLines: 2,
overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, // textAlign: TextAlign.end,
style: TextStyle( // style: TextStyle(
fontSize: 22.sp, // fontSize: 22.sp,
color: AppColors.darkGrayTextColor)), // color: AppColors.darkGrayTextColor)),
), // ),
)), // )),
/* 2024-01-12 by DaisyWu /* 2024-01-12 by DaisyWu
CommonItem( CommonItem(
leftTitel: leftTitel:

View File

@ -1,4 +1,3 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
@ -17,31 +16,36 @@ import '../../../../tools/eventBusEventManage.dart';
import '../../../../tools/storage.dart'; import '../../../../tools/storage.dart';
import 'burglarAlarm_state.dart'; import 'burglarAlarm_state.dart';
class BurglarAlarmLogic extends BaseGetXController{ class BurglarAlarmLogic extends BaseGetXController {
BurglarAlarmState state = BurglarAlarmState(); BurglarAlarmState state = BurglarAlarmState();
// -> // ->
Future<void> _setLockSetGeneralSetting() async{ Future<void> _setLockSetGeneralSetting() async {
final LoginEntity entity = await ApiRepository.to.setBurglarAlarmData( final LoginEntity entity = await ApiRepository.to.setBurglarAlarmData(
lockId: state.lockSetInfoData.value.lockId!, lockId: state.lockSetInfoData.value.lockId!,
antiPrySwitch:state.burglarAlarmEnable.value == 1 ? 0 : 1, // 1-2-; antiPrySwitch: state.burglarAlarmEnable.value == 1 ? 0 : 1, // 1-2-;
); );
if(entity.errorCode!.codeIsSuccessful){ if (entity.errorCode!.codeIsSuccessful) {
// eventBus.fire(RefreshLockListInfoDataEvent()); eventBus.fire(RefreshLockListInfoDataEvent());
state.burglarAlarmEnable.value = state.burglarAlarmEnable.value == 1 ? 0 : 1; state.burglarAlarmEnable.value =
state.lockSetInfoData.value.lockSettingInfo!.antiPrySwitch = state.burglarAlarmEnable.value; state.burglarAlarmEnable.value == 1 ? 0 : 1;
showToast('操作成功'.tr, something: (){ state.lockSetInfoData.value.lockSettingInfo!.antiPrySwitch =
eventBus.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); state.burglarAlarmEnable.value;
showToast('操作成功'.tr, something: () {
eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
}); });
} }
} }
// //
late StreamSubscription<Reply> _replySubscription; late StreamSubscription<Reply> _replySubscription;
void _initReplySubscription() { void _initReplySubscription() {
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) { _replySubscription =
if(reply is SetSupportFunctionsNoParametersReply) { EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
if (reply is SetSupportFunctionsNoParametersReply) {
_replySetSupportFunctionsWithParameters(reply); _replySetSupportFunctionsWithParameters(reply);
} }
@ -71,7 +75,7 @@ class BurglarAlarmLogic extends BaseGetXController{
// //
Future<void> _replySetSupportFunctionsWithParameters(Reply reply) async { Future<void> _replySetSupportFunctionsWithParameters(Reply reply) async {
final int status = reply.data[2]; final int status = reply.data[2];
switch(status){ switch (status) {
case 0x00: case 0x00:
// //
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
@ -91,41 +95,47 @@ class BurglarAlarmLogic extends BaseGetXController{
// () // ()
Future<void> sendBurglarAlarm() async { Future<void> sendBurglarAlarm() async {
if(state.sureBtnState.value == 1){ if (state.sureBtnState.value == 1) {
return; return;
} }
state.sureBtnState.value = 1; state.sureBtnState.value = 1;
EasyLoading.show(); EasyLoading.show();
showBlueConnetctToastTimer(action: (){ showBlueConnetctToastTimer(action: () {
dismissEasyLoading(); dismissEasyLoading();
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
}); });
BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async { BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) { if (connectionState == BluetoothConnectionState.connected) {
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey); final List<String>? privateKey =
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!); await Storage.getStringList(saveBluePrivateKey);
final List<int> getPrivateKeyList =
changeStringListToIntList(privateKey!);
final List<String>? token = await Storage.getStringList(saveBlueToken); final List<String>? token = await Storage.getStringList(saveBlueToken);
final List<int> getTokenList = changeStringListToIntList(token!); final List<int> getTokenList = changeStringListToIntList(token!);
final List<String>? publicKey = await Storage.getStringList(saveBluePublicKey); final List<String>? publicKey =
final List<int> getPublicKeyList = changeStringListToIntList(publicKey!); await Storage.getStringList(saveBluePublicKey);
final List<int> getPublicKeyList =
changeStringListToIntList(publicKey!);
IoSenderManage.setSupportFunctionsNoParametersCommand( IoSenderManage.setSupportFunctionsNoParametersCommand(
keyID: state.lockSetInfoData.value.lockBasicInfo!.keyId.toString(), keyID: state.lockSetInfoData.value.lockBasicInfo!.keyId.toString(),
userID: await Storage.getUid(), userID: await Storage.getUid(),
featureBit: 30, featureBit: 30,
featureEnable: state.burglarAlarmEnable.value == 1 ? 0 : 1, featureEnable: state.burglarAlarmEnable.value == 1 ? 0 : 1,
token: getTokenList, token: getTokenList,
needAuthor: 1, needAuthor: 1,
publicKey: getPublicKeyList, publicKey: getPublicKeyList,
privateKey: getPrivateKeyList); privateKey: getPrivateKeyList,
);
} else if (connectionState == BluetoothConnectionState.disconnected) { } else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading(); dismissEasyLoading();
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
if(state.ifCurrentScreen.value == true){ if (state.ifCurrentScreen.value == true) {
showBlueConnetctToast(); showBlueConnetctToast();
} }
} }
@ -152,5 +162,4 @@ class BurglarAlarmLogic extends BaseGetXController{
_replySubscription.cancel(); _replySubscription.cancel();
} }
} }

View File

@ -37,40 +37,46 @@ import 'configuringWifi_state.dart';
class ConfiguringWifiLogic extends BaseGetXController { class ConfiguringWifiLogic extends BaseGetXController {
final ConfiguringWifiState state = ConfiguringWifiState(); final ConfiguringWifiState state = ConfiguringWifiState();
final int _configurationTimeout = 60; //
/// WiFi锁服务IP和端口
Future<void> getWifiLockServiceIpAndPort() async { Future<void> getWifiLockServiceIpAndPort() async {
final ConfiguringWifiEntity entity = try {
await ApiRepository.to.getWifiLockServiceIpAndPort(); final ConfiguringWifiEntity entity =
if (entity.errorCode! == 0) { await ApiRepository.to.getWifiLockServiceIpAndPort();
state.configuringWifiEntity.value = entity; 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 peerId,
required String wifiName, required String wifiName,
required String secretKey, required String secretKey,
required String deviceMac, required String deviceMac,
required String networkMac, required String networkMac,
}) async { }) async {
final LoginEntity entity = await ApiRepository.to.settingDeviceNetwork( try {
deviceType: 2, final LoginEntity entity = await ApiRepository.to.settingDeviceNetwork(
deviceMac: deviceMac, deviceType: 2,
wifiName: wifiName, deviceMac: deviceMac,
networkMac: networkMac,
secretKey: secretKey,
peerId: peerId,
);
if (entity.errorCode!.codeIsSuccessful) {
// peerID
StartChartManage().lockNetworkInfo = DeviceNetworkInfo(
wifiName: wifiName, wifiName: wifiName,
networkMac: networkMac, networkMac: networkMac,
secretKey: secretKey, secretKey: secretKey,
peerId: peerId, peerId: peerId,
); );
return entity;
await _getUploadLockSet(); } catch (e) {
dismissEasyLoading();
state.sureBtnState.value = 0;
AppLog.log('网络配置异常:$e');
return LoginEntity();
} }
} }
@ -84,81 +90,165 @@ class ConfiguringWifiLogic extends BaseGetXController {
if (reply is GatewayConfiguringWifiResultReply) { if (reply is GatewayConfiguringWifiResultReply) {
_replySenderConfiguringWifiResult(reply); _replySenderConfiguringWifiResult(reply);
} }
// wifi配网命令应答结果
if (reply is GatewayConfiguringWifiReply) { if (reply is GatewayConfiguringWifiReply) {
_replySenderConfiguringWifiResult(reply); _replySenderConfiguringWifi(reply);
} }
if (reply is GatewayGetStatusReply) { if (reply is GatewayGetStatusReply) {
_replyGatewayGetStatusReply(reply); _replyGatewayGetStatusReply(reply);
} }
// if (reply is GatewayGetStatusReply) {
// _replyStatusInfo(reply);
// }
// //
if (reply is UpdataLockSetReply) { if (reply is UpdataLockSetReply) {
_replyUpdataLockSetReply(reply); _replyUpdataLockSetReply(reply);
} }
AppLog.log('蓝牙回调处理完毕${EasyLoading.isShow}');
}); });
} }
// WIFI配网结果 // WIFI配网操作结果处理
Future<void> _replySenderConfiguringWifiResult(Reply reply) async { Future<void> _replySenderConfiguringWifi(Reply reply) async {
final int status = reply.data[2]; final int status = reply.data[2];
// state.sureBtnState.value = 0;
// loading超时定时器
state.loadingTimer?.cancel();
state.loadingTimer = null;
switch (status) { switch (status) {
case 0x00: case 0x00:
await Storage.removeLockNetWorkInfoCache(); AppLog.log('wifi配网命令回复结果:成功');
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 ?? '');
break; break;
default: default:
// //
dismissEasyLoading(); // loading dismissEasyLoading(); // loading
cancelBlueConnetctToastTimer();
if (state.loadingTimer != null) {
state.loadingTimer!.cancel();
state.loadingTimer = null;
}
showToast('配网失败'.tr); showToast('配网失败'.tr);
state.isLoading.value = false; state.isLoading.value = false;
break; 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());
eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true));
});
//
_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) { String prettyPrintJson(String jsonString) {
var jsonObject = json.decode(jsonString); var jsonObject = json.decode(jsonString);
return JsonEncoder.withIndent(' ').convert(jsonObject); return JsonEncoder.withIndent(' ').convert(jsonObject);
@ -168,80 +258,111 @@ class ConfiguringWifiLogic extends BaseGetXController {
Future<void> senderConfiguringWifiAction() async { Future<void> senderConfiguringWifiAction() async {
AppLog.log('开始配网${EasyLoading.isShow}'); AppLog.log('开始配网${EasyLoading.isShow}');
if (state.isLoading.isTrue) { if (state.sureBtnState.value == 1) {
AppLog.log('正在配网中请勿重复点击'); AppLog.log('正在配网中请勿重复点击');
return; 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; return;
} }
if (state.wifiPWDController.text.isEmpty) { // sureBtnState状态
showToast('请输入WiFi密码'.tr); state.sureBtnState.value = 1;
return;
}
// if (state.sureBtnState.value == 1) {
// return;
// }
// state.sureBtnState.value = 1;
final GetGatewayConfigurationEntity entity = // loading
await ApiRepository.to.getGatewayConfigurationNotLoading(timeout: 60); if (!EasyLoading.isShow) {
if (entity.errorCode!.codeIsSuccessful) { showEasyLoading();
state.getGatewayConfigurationStr = entity.data ?? '';
} }
// //
final loginData = await Storage.getLoginData(); showBlueConnetctToastTimer(
action: () {
// app用户的peerId if (EasyLoading.isShow) {
String appPeerId = loginData?.starchart?.starchartId ?? ''; dismissEasyLoading();
// }
if (state.getGatewayConfigurationStr.isNotEmpty) { state.sureBtnState.value = 0; //
// JSON Map },
Map<String, dynamic> jsonMap = outTimer: 30,
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;
});
// //
BlueManage().blueSendData( BlueManage().blueSendData(
BlueManage().connectDeviceName, BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async { (BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) { if (connectionState == BluetoothConnectionState.connected) {
IoSenderManage.gatewayConfiguringWifiCommand( try {
ssid: state.wifiNameController.text, IoSenderManage.gatewayConfiguringWifiCommand(
password: state.wifiPWDController.text, ssid: state.wifiNameController.text,
gatewayConfigurationStr: state.getGatewayConfigurationStr, 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) { } else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading(); if (EasyLoading.isShow) {
dismissEasyLoading();
}
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
state.isLoading.value = false; state.sureBtnState.value = 0; //
// state.sureBtnState.value = 0;
if (state.ifCurrentScreen.value == true) { if (state.ifCurrentScreen.value == true) {
showBlueConnetctToast(); showBlueConnetctToast();
} }
@ -249,7 +370,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
}, },
isAddEquipment: false, isAddEquipment: false,
); );
state.isLoading.value = true;
} }
// //
@ -278,11 +398,15 @@ class ConfiguringWifiLogic extends BaseGetXController {
final NetworkInfo _networkInfo = NetworkInfo(); final NetworkInfo _networkInfo = NetworkInfo();
Future<String> getWifiName() async { Future<String> getWifiName() async {
String ssid = ''; try {
ssid = (await _networkInfo.getWifiName())!; String? ssid = await _networkInfo.getWifiName();
ssid = ssid ?? ''; ssid = ssid ?? '';
ssid = ssid.replaceAll(r'"', ''); ssid = ssid.replaceAll(r'"', '');
return ssid ?? ''; return ssid;
} catch (e) {
AppLog.log('获取WiFi名称失败: $e');
return '';
}
} }
/// ///
@ -308,17 +432,12 @@ class ConfiguringWifiLogic extends BaseGetXController {
getWifiLockServiceIpAndPort(); getWifiLockServiceIpAndPort();
_initReplySubscription(); _initReplySubscription();
// getDevicesStatusAction();
}
@override
void onInit() {
super.onInit();
} }
@override @override
void onClose() { void onClose() {
_replySubscription.cancel(); _replySubscription.cancel();
cancelBlueConnetctToastTimer(); //
super.onClose(); super.onClose();
} }
@ -330,8 +449,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
switch (status) { switch (status) {
case 0x00: case 0x00:
// //
// state.sureBtnState.value = 0;
final GetGatewayInfoModel gatewayModel = GetGatewayInfoModel(); final GetGatewayInfoModel gatewayModel = GetGatewayInfoModel();
// MAC地址 // MAC地址
int index = 3; int index = 3;
@ -372,25 +489,16 @@ class ConfiguringWifiLogic extends BaseGetXController {
default: default:
// //
dismissEasyLoading(); dismissEasyLoading();
showToast('配网失败'.tr); // showToast('获取设备状态失败'.tr);
if (state.loadingTimer != null) {
state.loadingTimer!.cancel();
state.loadingTimer = null;
}
break; break;
} }
} }
// //
Future<void> _getUploadLockSet() async { Future<void> _getUploadLockSet() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
final List<String>? token = await Storage.getStringList(saveBlueToken); final List<String>? token = await Storage.getStringList(saveBlueToken);
final List<int> getTokenList = changeStringListToIntList(token!); final List<int> getTokenList = changeStringListToIntList(token!);
//
await _uploadLockSet(getTokenList); await _uploadLockSet(getTokenList);
} }
@ -398,44 +506,74 @@ class ConfiguringWifiLogic extends BaseGetXController {
Future<void> _uploadLockSet(List<int> token) async { Future<void> _uploadLockSet(List<int> token) async {
final List<String>? privateKey = final List<String>? privateKey =
await Storage.getStringList(saveBluePrivateKey); 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<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( BlueManage().blueSendData(BlueManage().connectDeviceName,
lockID: BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async {
userID: await Storage.getUid(), if (connectionState == BluetoothConnectionState.connected) {
token: token, IoSenderManage.updataLockSetCommand(
needAuthor: 1, lockID: BlueManage().connectDeviceName,
signKey: signKeyDataList, userID: await Storage.getUid(),
privateKey: getPrivateKeyList); 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 { Future<void> _replyUpdataLockSetReply(Reply reply) async {
final int status = reply.data[2]; final int status = reply.data[2];
dismissEasyLoading(); // loading // loading状态直到整个过程完成
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
switch (status) { switch (status) {
case 0x00: case 0x00:
await _lockDataUpload( await _lockDataUpload(
uploadType: 1, uploadType: 1,
recordType: 0, recordType: 0,
records: reply.data.sublist(7, reply.data.length)); records: reply.data.sublist(7, reply.data.length));
break; break;
case 0x06: case 0x06:
// //token
final List<int> token = reply.data.sublist(3, 7); try {
final List<String> saveStrList = changeIntListToStringList(token); final List<int> token = reply.data.sublist(3, 7);
Storage.setStringList(saveBlueToken, saveStrList); final List<String> saveStrList = changeIntListToStringList(token);
await Storage.setStringList(saveBlueToken, saveStrList);
_uploadLockSet(token); _uploadLockSet(token);
} catch (e) {
if (EasyLoading.isShow) {
dismissEasyLoading(); // loading
}
// showToast('获取设置权限失败:${e.toString()}'.tr);
state.sureBtnState.value = 0; //
}
break; break;
default: default:
dismissEasyLoading(); if (EasyLoading.isShow) {
cancelBlueConnetctToastTimer(); dismissEasyLoading(); // loading
}
// showToast('获取锁设置失败 (错误码: $status)'.tr);
state.sureBtnState.value = 0; //
break; break;
} }
} }
@ -451,19 +589,10 @@ class ConfiguringWifiLogic extends BaseGetXController {
recordType: recordType, recordType: recordType,
records: records, records: records,
isUnShowLoading: true); 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 if (entity.errorCode!.codeIsSuccessful) {
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); eventBus
eventBus.fire(SuccessfulDistributionNetwork()); .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
});
} }
} }
} }

View File

@ -25,6 +25,9 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
final ConfiguringWifiLogic logic = Get.put(ConfiguringWifiLogic()); final ConfiguringWifiLogic logic = Get.put(ConfiguringWifiLogic());
final ConfiguringWifiState state = Get.find<ConfiguringWifiLogic>().state; final ConfiguringWifiState state = Get.find<ConfiguringWifiLogic>().state;
//
final RxBool _obscureText = true.obs;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -39,19 +42,36 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
'WiFi名称'.tr, '请输入WiFi名字'.tr, state.wifiNameController), 'WiFi名称'.tr, '请输入WiFi名字'.tr, state.wifiNameController),
Container( Container(
width: 1.sw, height: 1.h, color: AppColors.mainBackgroundColor), width: 1.sw, height: 1.h, color: AppColors.mainBackgroundColor),
configuringWifiTFWidget( configuringWifiPasswordTFWidget(
'WiFi密码'.tr, '请输入WiFi密码'.tr, state.wifiPWDController), 'WiFi密码'.tr, '请输入WiFi密码'.tr, state.wifiPWDController),
SizedBox( SizedBox(
height: 50.h, height: 50.h,
), ),
Obx( Obx(
() => SubmitBtn( () => SubmitBtn(
btnName: '确定'.tr, btnName: state.sureBtnState.value == 1 ? '配置中...'.tr : '确定'.tr,
isDisabled: state.isLoading.isFalse, // sureBtnState为1时按钮不可用
onClick: state.isLoading.isTrue isDisabled: state.sureBtnState.value == 0,
onClick: state.sureBtnState.value == 1
? null ? null
: () { : () {
FocusScope.of(context).requestFocus(FocusNode()); 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(); 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) { Widget getTFWidget(String tfStr, TextEditingController controller) {
return Container( return Container(
height: 65.h, height: 65.h,
@ -95,18 +130,14 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextField( child: TextField(
//
maxLines: 1, maxLines: 1,
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.deny('\n'), FilteringTextInputFormatter.deny('\n'),
// LengthLimitingTextInputFormatter(30),
], ],
controller: controller, controller: controller,
autofocus: false, autofocus: false,
textAlign: TextAlign.end, textAlign: TextAlign.end,
decoration: InputDecoration( decoration: InputDecoration(
//
// contentPadding: const EdgeInsets.only(top: 12.0, bottom: 8.0),
hintText: tfStr, hintText: tfStr,
hintStyle: TextStyle(fontSize: 22.sp), hintStyle: TextStyle(fontSize: 22.sp),
focusedBorder: const OutlineInputBorder( 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 @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();

View File

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:network_info_plus/network_info_plus.dart'; import 'package:network_info_plus/network_info_plus.dart';
@ -33,5 +31,4 @@ class ConfiguringWifiState {
String getGatewayConfigurationStr = ''; String getGatewayConfigurationStr = '';
RxBool isLoading = false.obs; 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:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.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_gateway/io_gateway_getWifiList.dart';
import 'package:star_lock/blue/io_protocol/io_getWifiList.dart'; import 'package:star_lock/blue/io_protocol/io_getWifiList.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart';
@ -20,7 +21,9 @@ class WifiListLogic extends BaseGetXController {
// //
late StreamSubscription<Reply> _replySubscription; late StreamSubscription<Reply> _replySubscription;
Timer? _connectionTimer;
///
void _initReplySubscription() { void _initReplySubscription() {
_replySubscription = _replySubscription =
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) { EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
@ -31,78 +34,130 @@ class WifiListLogic extends BaseGetXController {
if (reply is GatewayGetWifiListReply) { if (reply is GatewayGetWifiListReply) {
_replyGetWifiListParameters(reply); _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 { Future<void> _replySendGetWifiParameters(Reply reply) async {
final int status = reply.data[2]; final int status = reply.data[2];
switch (status) { switch (status) {
case 0x00: case 0x00:
// // - loading框UI中已经有进度指示器
showEasyLoading();
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
Future.delayed(5.seconds, dismissEasyLoading);
break; break;
case 0x06: case 0x06:
// //
dismissEasyLoading();
AppLog.log('需要设备鉴权,请重试'.tr);
state.sureBtnState.value = 0;
break; break;
default: default:
//
dismissEasyLoading();
AppLog.log('获取WiFi列表失败错误码$status'.tr);
state.sureBtnState.value = 0;
break; break;
} }
} }
// WiFi数据解析 /// WiFi数据解析
Future<void> _replyGetWifiListParameters(Reply reply) async { Future<void> _replyGetWifiListParameters(Reply reply) async {
final int status = reply.data[2]; final int status = reply.data[2];
switch (status) { switch (status) {
case 0x00: case 0x00:
// //
// showEasyLoading();
dismissEasyLoading(); dismissEasyLoading();
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
if (reply.data[3] > 0) { if (reply.data[3] > 0) {
reply.data.removeRange(0, 4); reply.data.removeRange(0, 4);
// 33 // 33
final List<List<int>> getList = splitList(reply.data, 33); final List<List<int>> getList = splitList(reply.data, 33);
final List<Map<String, String>> uploadList = <Map<String, String>>[]; final List<Map<String, String>> uploadList = <Map<String, String>>[];
for (int i = 0; i < getList.length; i++) { for (int i = 0; i < getList.length; i++) {
final List<int> indexList = getList[i]; final List<int> indexList = getList[i];
final Map<String, String> indexMap = <String, String>{}; final Map<String, String> indexMap = <String, String>{};
final List<int> wifiName = indexList.sublist(0, 32); 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(); indexMap['rssi'] = (indexList.last - 255).toString();
uploadList.add(indexMap); 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; break;
default: default:
//
dismissEasyLoading();
showToast('解析WiFi列表失败错误码$status'.tr);
state.sureBtnState.value = 0;
break; break;
} }
} }
// wifi列表 /// WiFi列表
Future<void> senderGetWifiListWifiAction() async { Future<void> senderGetWifiListWifiAction() async {
if (state.sureBtnState.value == 1) { if (state.sureBtnState.value == 1) {
return; return;
} }
state.sureBtnState.value = 1; state.sureBtnState.value = 1;
state.wifiNameDataList.clear(); //
showEasyLoading(); // loading框UI中已经有进度指示器
showBlueConnetctToastTimer(action: () { showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
}); });
BlueManage().blueSendData(BlueManage().connectDeviceName, BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async { (BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) { if (connectionState == BluetoothConnectionState.connected) {
IoSenderManage.gatewayGetWifiCommand( try {
userID: await Storage.getUid(), IoSenderManage.gatewayGetWifiCommand(
); userID: await Storage.getUid(),
);
} catch (e) {
state.sureBtnState.value = 0;
cancelBlueConnetctToastTimer();
showToast('发送获取WiFi列表请求失败${e.toString()}'.tr);
}
} else if (connectionState == BluetoothConnectionState.disconnected) { } else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
state.sureBtnState.value = 0; state.sureBtnState.value = 0;
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
if (state.ifCurrentScreen.value == true) { if (state.ifCurrentScreen.value == true) {
@ -113,22 +168,27 @@ class WifiListLogic extends BaseGetXController {
} }
@override @override
void onReady() { void onReady() async {
super.onReady(); super.onReady();
_initReplySubscription(); _initReplySubscription();
await senderGetWifiListWifiAction();
dismissEasyLoading();
} }
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
//
senderGetWifiListWifiAction(); state.ifCurrentScreen.value = true;
} }
@override @override
void onClose() { void onClose() {
super.onClose(); //
_replySubscription.cancel(); _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 WifiListLogic logic = Get.put(WifiListLogic());
final WifiListState state = Get.find<WifiListLogic>().state; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(
@ -38,13 +64,15 @@ class _WifiListPageState extends State<WifiListPage> {
barTitle: 'WIFI列表'.tr, barTitle: 'WIFI列表'.tr,
haveBack: state.pageName.value == 'lockSet', haveBack: state.pageName.value == 'lockSet',
actionsList: <Widget>[ actionsList: <Widget>[
TextButton( Obx(() => TextButton(
child: Text( child: Text(
'刷新'.tr, '刷新'.tr,
style: TextStyle(color: Colors.white, fontSize: 24.sp), style: TextStyle(color: Colors.white, fontSize: 24.sp),
), ),
onPressed: logic.senderGetWifiListWifiAction, onPressed: state.sureBtnState.value == 0
), ? logic.senderGetWifiListWifiAction
: null,
)),
], ],
backgroundColor: AppColors.mainColor, backgroundColor: AppColors.mainColor,
), ),
@ -52,26 +80,56 @@ class _WifiListPageState extends State<WifiListPage> {
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Obx(() => state.wifiNameDataList.value.isNotEmpty child: Obx(() => state.wifiNameDataList.value.isNotEmpty
? ListView.builder( ? RefreshIndicator(
itemCount: state.wifiNameDataList.value.length, onRefresh: () async {
itemBuilder: (BuildContext c, int index) { if (state.sureBtnState.value == 0) {
Map wifiNameStr = state.wifiNameDataList.value[index]; await logic.senderGetWifiListWifiAction();
return _messageListItem( }
wifiNameStr['wifiName'], wifiNameStr['rssi'], () { },
Get.toNamed(Routers.configuringWifiPage, child: ListView.builder(
arguments: { itemCount: state.wifiNameDataList.value.length,
'lockSetInfoData': itemBuilder: (BuildContext c, int index) {
state.lockSetInfoData.value, Map wifiNameStr =
'wifiName': wifiNameStr['wifiName'], state.wifiNameDataList.value[index];
'pageName': state.pageName.value, return _messageListItem(
}); wifiNameStr['wifiName'], wifiNameStr['rssi'],
}); () {
}) Get.toNamed(Routers.configuringWifiPage,
: NoData( arguments: {
noDataHeight: 1.sh - 'lockSetInfoData':
ScreenUtil().statusBarHeight - state.lockSetInfoData.value,
ScreenUtil().bottomBarHeight - 'wifiName': wifiNameStr['wifiName'],
64.h)), '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' state.pageName.value == 'saveLock'
? SubmitBtn( ? SubmitBtn(
@ -140,24 +198,37 @@ class _WifiListPageState extends State<WifiListPage> {
height: 79.h, height: 79.h,
width: 1.sw - 20.w * 2, width: 1.sw - 20.w * 2,
child: Row( child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Flexible( Flexible(
flex: 4,
child: Text( child: Text(
'$wifiName(${rssi}db)', wifiName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 22.sp, color: AppColors.blackColor), fontSize: 24.sp, color: AppColors.blackColor),
), ),
), ),
// Text( Flexible(
// rssi, flex: 1,
// maxLines: 1, child: Row(
// overflow: TextOverflow.ellipsis, mainAxisAlignment: MainAxisAlignment.end,
// style: TextStyle( children: [
// fontSize: 22.sp, color: AppColors.blackColor), 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

@ -118,6 +118,15 @@ class _NormallyOpenModePageState extends State<NormallyOpenModePage> with RouteA
: SubmitBtn( : SubmitBtn(
btnName: '保存'.tr, btnName: '保存'.tr,
onClick: () { onClick: () {
if (state.weekDays.value.isEmpty) {
logic.showToast('请选择常开日期'.tr);
return;
}
if (state.endTimeMinute.value < state.beginTimeMinute.value) {
logic.showToast('结束时间不能小于开始时间哦'.tr);
return;
}
logic.sendAutoLock(); logic.sendAutoLock();
}), }),
)), )),

View File

@ -21,9 +21,10 @@ class RemoteUnlockingLogic extends BaseGetXController {
RemoteUnlockingState state = RemoteUnlockingState(); RemoteUnlockingState state = RemoteUnlockingState();
void remoteUnlockingOpenOrClose() async { void remoteUnlockingOpenOrClose() async {
final LoginEntity entity = await ApiRepository.to.remoteUnlockingOpenOrClose( final LoginEntity entity = await ApiRepository.to
lockId: state.lockSetInfoData.value.lockId!, .remoteUnlockingOpenOrClose(
remoteUnlock: state.remoteEnable.value == 1 ? 0 : 1); lockId: state.lockSetInfoData.value.lockId!,
remoteUnlock: state.remoteEnable.value == 1 ? 0 : 1);
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
showToast('操作成功'.tr, something: () { showToast('操作成功'.tr, something: () {
eventBus.fire(RefreshLockListInfoDataEvent()); eventBus.fire(RefreshLockListInfoDataEvent());
@ -32,7 +33,6 @@ class RemoteUnlockingLogic extends BaseGetXController {
state.remoteEnable.value; state.remoteEnable.value;
eventBus eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
eventBus.fire(RefreshLockListInfoDataEvent());
eventBus.fire(LockSetChangeSetRefreshLockDetailWithType( eventBus.fire(LockSetChangeSetRefreshLockDetailWithType(
5, 5,
state.lockSetInfoData.value.lockSettingInfo!.remoteUnlock! state.lockSetInfoData.value.lockSettingInfo!.remoteUnlock!
@ -44,6 +44,7 @@ class RemoteUnlockingLogic extends BaseGetXController {
// //
late StreamSubscription<Reply> _replySubscription; late StreamSubscription<Reply> _replySubscription;
void _initReplySubscription() { void _initReplySubscription() {
_replySubscription = _replySubscription =
EventBusManager().eventBus!.on<Reply>().listen((reply) { EventBusManager().eventBus!.on<Reply>().listen((reply) {

View File

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

View File

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

View File

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

View File

@ -361,6 +361,7 @@ class Bluetooth {
class LockFeature { class LockFeature {
LockFeature({ LockFeature({
this.password, this.password,
this.passwordIssue,
this.icCard, this.icCard,
this.fingerprint, this.fingerprint,
this.fingerVein, this.fingerVein,
@ -374,10 +375,14 @@ class LockFeature {
this.isNoSupportedBlueBroadcast, this.isNoSupportedBlueBroadcast,
this.wifiLockType, this.wifiLockType,
this.wifi, this.wifi,
this.isH264,
this.isH265,
this.isMJpeg,
}); });
LockFeature.fromJson(Map<String, dynamic> json) { LockFeature.fromJson(Map<String, dynamic> json) {
password = json['password']; password = json['password'];
passwordIssue = json['passwordIssue'];
icCard = json['icCard']; icCard = json['icCard'];
fingerprint = json['fingerprint']; fingerprint = json['fingerprint'];
fingerVein = json['fingerVein']; fingerVein = json['fingerVein'];
@ -391,9 +396,13 @@ class LockFeature {
isNoSupportedBlueBroadcast = json['isNoSupportedBlueBroadcast']; isNoSupportedBlueBroadcast = json['isNoSupportedBlueBroadcast'];
wifiLockType = json['wifiLockType']; wifiLockType = json['wifiLockType'];
wifi = json['wifi']; wifi = json['wifi'];
isH264 = json['isH264'];
isH265 = json['isH265'];
isMJpeg = json['isMJpeg'];
} }
int? password; int? password;
int? passwordIssue;
int? icCard; int? icCard;
int? fingerprint; int? fingerprint;
int? fingerVein; int? fingerVein;
@ -407,10 +416,14 @@ class LockFeature {
int? isNoSupportedBlueBroadcast; int? isNoSupportedBlueBroadcast;
int? wifiLockType; int? wifiLockType;
int? wifi; int? wifi;
int? isH264;
int? isH265;
int? isMJpeg;
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['password'] = password; data['password'] = password;
data['passwordIssue'] = passwordIssue;
data['icCard'] = icCard; data['icCard'] = icCard;
data['fingerprint'] = fingerprint; data['fingerprint'] = fingerprint;
data['fingerVein'] = fingerVein; data['fingerVein'] = fingerVein;
@ -424,6 +437,9 @@ class LockFeature {
data['isNoSupportedBlueBroadcast'] = isNoSupportedBlueBroadcast; data['isNoSupportedBlueBroadcast'] = isNoSupportedBlueBroadcast;
data['wifiLockType'] = wifiLockType; data['wifiLockType'] = wifiLockType;
data['wifi'] = wifi; data['wifi'] = wifi;
data['isH264'] = isH264;
data['isH265'] = isH265;
data['isMJpeg'] = isMJpeg;
return data; return data;
} }
} }

View File

@ -27,13 +27,13 @@ class LockListLogic extends BaseGetXController {
LockListLogic(this.entity) {} LockListLogic(this.entity) {}
LockListState state = LockListState(); LockListState state = LockListState();
List<GroupList> _groupDataList = <GroupList>[]; final RxList<GroupList> groupDataList = <GroupList>[].obs;
LockListInfoGroupEntity? entity; LockListInfoGroupEntity? entity;
final ShowTipView showTipView = ShowTipView(); final ShowTipView showTipView = ShowTipView();
List<GroupList> get groupDataList { List<GroupList> get groupDataListFiltered {
final List<GroupList> list = final List<GroupList> list =
_groupDataList.map((GroupList e) => e.copy()).toList(); groupDataList.map((GroupList e) => e.copy()).toList();
if (state.searchStr.value != '' && state.showSearch.value) { if (state.searchStr.value != '' && state.showSearch.value) {
list.forEach((GroupList element) { list.forEach((GroupList element) {
element.lockList?.removeWhere((LockListInfoItemEntity element) => element.lockList?.removeWhere((LockListInfoItemEntity element) =>
@ -60,15 +60,12 @@ class LockListLogic extends BaseGetXController {
// //
void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) { void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) {
this.entity = entity; this.entity = entity;
if (entity.pageNo == 1) { groupDataList.value = entity.groupList!;
_groupDataList = <GroupList>[];
}
_groupDataList.addAll(entity.groupList!);
update();
} }
// //
late StreamSubscription<Reply> _replySubscription; late StreamSubscription<Reply> _replySubscription;
late StreamSubscription _setLockListInfoGroupEntity;
void _initReplySubscription() { void _initReplySubscription() {
_replySubscription = _replySubscription =
@ -336,17 +333,30 @@ class LockListLogic extends BaseGetXController {
void onReady() { void onReady() {
super.onReady(); super.onReady();
_initReplySubscription(); _initReplySubscription();
_initEventHandler();
} }
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
// AppLog.log('onInit调用了 setLockListInfoGroupEntity'); AppLog.log('[onInit] entity: \\${entity?.toString()}');
setLockListInfoGroupEntity(entity!); if (entity != null) {
setLockListInfoGroupEntity(entity!);
}
_initEventHandler();
} }
@override @override
void onClose() { void onClose() {
_replySubscription.cancel(); _replySubscription.cancel();
_setLockListInfoGroupEntity.cancel();
}
void _initEventHandler() {
_setLockListInfoGroupEntity = eventBus
.on<SetLockListInfoGroupEntity>()
.listen((SetLockListInfoGroupEntity event) async {
setLockListInfoGroupEntity(event.lockListInfoGroupEntity);
});
} }
} }

View File

@ -37,32 +37,31 @@ class _LockListPageState extends State<LockListPage> with RouteAware {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<LockListLogic>(builder: (LockListLogic logic) { return Obx(() => Scaffold(
return Scaffold( body: ListView.separated(
body: ListView.separated( itemCount: logic.groupDataListFiltered.length,
itemCount: logic.groupDataList.length, itemBuilder: (BuildContext context, int index) {
itemBuilder: (BuildContext context, int index) { final GroupList itemData = logic.groupDataListFiltered[index];
final GroupList itemData = logic.groupDataList[index]; return _buildLockExpandedList(context, index, itemData, key: ValueKey(itemData.groupId));
return _buildLockExpandedList(context, index, itemData); },
}, shrinkWrap: true,
shrinkWrap: true, physics: const AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(), separatorBuilder: (BuildContext context, int index) {
separatorBuilder: (BuildContext context, int index) { return const Divider(
return const Divider( height: 1,
height: 1, color: AppColors.greyLineColor,
color: AppColors.greyLineColor, );
); }),
}), ));
);
});
} }
// //
Widget _buildLockExpandedList(BuildContext context, int index, Widget _buildLockExpandedList(BuildContext context, int index,
GroupList itemData) { GroupList itemData, {Key? key}) {
final List<LockListInfoItemEntity> lockItemList = final List<LockListInfoItemEntity> lockItemList =
itemData.lockList ?? <LockListInfoItemEntity>[]; itemData.lockList ?? <LockListInfoItemEntity>[];
return LockListGroupView( return LockListGroupView(
key: key,
onTap: () { onTap: () {
// //
if (itemData.isChecked) {} else {} if (itemData.isChecked) {} else {}

View File

@ -132,20 +132,21 @@ class LockMainLogic extends BaseGetXController {
state.lockListInfoGroupEntity.refresh(); state.lockListInfoGroupEntity.refresh();
// AppLog.log('entity:$entity state.lockListInfoGroupEntity.value.groupList!.length:${state.lockListInfoGroupEntity.value.groupList![0].lockList!.length}'); // AppLog.log('entity:$entity state.lockListInfoGroupEntity.value.groupList!.length:${state.lockListInfoGroupEntity.value.groupList![0].lockList!.length}');
// //
if (Get.isRegistered<LockListLogic>()) { eventBus.fire(SetLockListInfoGroupEntity(lockListInfoGroupEntity: entity));
// // if (Get.isRegistered<LockListLogic>()) {
// AppLog.log('检测控制器是否存 调用了 setLockListInfoGroupEntity'); // //
Get.find<LockListLogic>().setLockListInfoGroupEntity(entity); // // AppLog.log('检测控制器是否存 调用了 setLockListInfoGroupEntity');
} else { // Get.find<LockListLogic>().setLockListInfoGroupEntity(entity);
// // } else {
Future<dynamic>.delayed(200.milliseconds, () { // //
if (Get.isRegistered<LockListLogic>()) { // Future<dynamic>.delayed(500.milliseconds, () {
// // if (Get.isRegistered<LockListLogic>()) {
// AppLog.log('检测控制器是否存 延迟调用了 setLockListInfoGroupEntity'); // //
Get.find<LockListLogic>().setLockListInfoGroupEntity(entity); // // AppLog.log('检测控制器是否存 延迟调用了 setLockListInfoGroupEntity');
} // Get.find<LockListLogic>().setLockListInfoGroupEntity(entity);
}); // }
} // });
// }
if (state.dataLength.value == 1) { if (state.dataLength.value == 1) {
if (Get.isRegistered<LockDetailLogic>()) { if (Get.isRegistered<LockDetailLogic>()) {

View File

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

View File

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

View File

@ -16,4 +16,8 @@ class TalkConstant {
videoType: [VideoTypeE.H264], videoType: [VideoTypeE.H264],
audioType: [AudioTypeE.G711], 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}'; 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() { String serialize() {
final bytes = <int>[]; final bytes = <int>[];
@ -98,16 +110,19 @@ class ScpMessage {
if (SpIndex != null) { if (SpIndex != null) {
bytes.add(SpIndex!); bytes.add(SpIndex!);
} }
// FromPeerId (44)
// FromPeerId ()
if (FromPeerId != null) { if (FromPeerId != null) {
bytes.addAll(utf8.encode(FromPeerId!)); bytes.addAll(utf8.encode(FromPeerId!));
} }
// FromPeerId (44)
// bytes.addAll(encodeFixedLengthString(FromPeerId, 44));
// ToPeerId (32) // ToPeerId (44)
if (ToPeerId != null) { if (ToPeerId != null) {
bytes.addAll(utf8.encode(ToPeerId!)); bytes.addAll(utf8.encode(ToPeerId!));
} }
// ToPeerId (44)
// bytes.addAll(encodeFixedLengthString(ToPeerId, 44));
// PayloadType (2 bytes) // PayloadType (2 bytes)
if (PayloadType != null) { if (PayloadType != null) {

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.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_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.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); EasyLoading.showToast(scpMessage.Payload, duration: 2000.milliseconds);
} else { } else {
talkDataRepository.addTalkData( 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_easyloading/flutter_easyloading.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:get/get.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/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.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/entity/scp_message.dart';
@ -13,6 +14,8 @@ 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/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_accept.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/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/storage.dart';
import '../../star_chart_manage.dart'; import '../../star_chart_manage.dart';
@ -32,7 +35,7 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
// //
startChartManage.stopTalkAcceptTimer(); startChartManage.stopTalkAcceptTimer();
// //
_handleSendExpect(); _handleSendExpect(lockPeerID: scpMessage.FromPeerId!);
// //
stopRingtone(); stopRingtone();
// //
@ -76,8 +79,48 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
} }
} }
void _handleSendExpect() { ///
// void _handleSendExpect({
startChartManage.sendImageVideoAndG711AudioTalkExpectData(); required String lockPeerID,
}) async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerID) {
isH264 = lockInfo.lockFeature?.isH264 == 1;
isMJpeg = lockInfo.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/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.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/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_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
@ -61,9 +62,6 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
int? spTotal, int? spTotal,
int? spIndex, int? spIndex,
int? messageId}) { int? messageId}) {
//
final stats = PacketLossStatistics().getStatistics();
_asyncLog('丢包统计: $stats');
// _asyncLog( // _asyncLog(
// '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength'); // '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength');
if (messageType == MessageTypeConstant.RealTimeData) { if (messageType == MessageTypeConstant.RealTimeData) {
@ -118,7 +116,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
void _handleVideoH264(TalkData talkData) { void _handleVideoH264(TalkData talkData) {
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame(); final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
talkDataH264Frame.mergeFromBuffer(talkData.content); 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)); await _processCompletePayload(Uint8List.fromList(talkData.content));
processCompletePayload.forEach((element) { processCompletePayload.forEach((element) {
talkData.content = element; talkData.content = element;
talkDataRepository.addTalkData(talkData); talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
}); });
} }
@ -138,7 +140,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
// // pcm数据 // // pcm数据
// List<int> pcmBytes = G711().convertList(g711Data); // List<int> pcmBytes = G711().convertList(g711Data);
// talkData.content = pcmBytes; // talkData.content = pcmBytes;
talkDataRepository.addTalkData(talkData); talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
} catch (e) { } catch (e) {
print('Error decoding G.711 to PCM: $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/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.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/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.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/entity/scp_message.dart';
@ -20,7 +21,7 @@ import '../../star_chart_manage.dart';
class UdpTalkExpectHandler extends ScpMessageBaseHandle class UdpTalkExpectHandler extends ScpMessageBaseHandle
implements ScpMessageHandler { implements ScpMessageHandler {
final TalkViewState talkViewState = Get.put(TalkViewLogic()).state; // final TalkViewState talkViewState = Get.put(TalkViewLogic()).state;
@override @override
void handleReq(ScpMessage scpMessage) { void handleReq(ScpMessage scpMessage) {
@ -40,7 +41,11 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle
startChartManage.stopTalkExpectMessageTimer(); startChartManage.stopTalkExpectMessageTimer();
// //
startChartManage.stopCallRequestMessageTimer(); startChartManage.stopCallRequestMessageTimer();
talkViewState.rotateAngle.value = talkExpectResp.rotate ?? 0; // talkViewState.rotateAngle.value = talkExpectResp.rotate ?? 0;
startChartManage.rotateAngle = talkExpectResp.rotate;
startChartManage.videoWidth = talkExpectResp.width;
startChartManage.videoHeight = talkExpectResp.height;
AppLog.log('视频画面需要旋转:${talkExpectResp.rotate},画面宽高:${talkExpectResp.width}-${talkExpectResp.height}');
// //
// x秒内没有收到通话保持则执行的操作; // x秒内没有收到通话保持则执行的操作;
talkePingOverTimeTimerManager.start(); talkePingOverTimeTimerManager.start();

View File

@ -1,21 +1,19 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/appRouters.dart'; import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.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/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.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/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_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_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/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.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/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/push/xs_jPhush.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
import 'package:star_lock/translations/current_locale_tool.dart'; import 'package:star_lock/translations/current_locale_tool.dart';
@ -25,27 +23,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
RxString currentLanguage = RxString currentLanguage =
CurrentLocaleTool.getCurrentLocaleString().obs; // CurrentLocaleTool.getCurrentLocaleString().obs; //
//
int _lastRequestTime = 0;
@override @override
void handleReq(ScpMessage scpMessage) async { 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(); final loginData = await Storage.getLoginData();
// //
if (loginData != null && if (loginData != null &&
(talkStatus.status != TalkStatus.passiveCallWaitingAnswer || (talkStatus.status != TalkStatus.passiveCallWaitingAnswer ||
@ -56,7 +37,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
startChartManage.ToPeerId = scpMessage.FromPeerId!; startChartManage.ToPeerId = scpMessage.FromPeerId!;
startChartManage.lockPeerId = scpMessage.FromPeerId!; startChartManage.lockPeerId = scpMessage.FromPeerId!;
// //
_talkRequestEvent(talkObjectName: talkReq.callerName); _talkRequestEvent(
talkObjectName: talkReq.callerName,
lockPeerID: scpMessage.FromPeerId!,
);
// //
replySuccessMessage(scpMessage); replySuccessMessage(scpMessage);
@ -75,6 +59,11 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
// //
startChartManage.FromPeerId = scpMessage.ToPeerId!; startChartManage.FromPeerId = scpMessage.ToPeerId!;
startChartManage.ToPeerId = scpMessage.FromPeerId!; startChartManage.ToPeerId = scpMessage.FromPeerId!;
startChartManage.lockPeerId = scpMessage.FromPeerId!;
//
_handleResponseSendExpect(
lockPeerID: scpMessage.FromPeerId!,
);
// //
startChartManage.startTalkExpectTimer(); startChartManage.startTalkExpectTimer();
// //
@ -95,28 +84,56 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
void handleRealTimeData(ScpMessage scpMessage) {} void handleRealTimeData(ScpMessage scpMessage) {}
// //
void _talkRequestEvent({required String talkObjectName}) { void _talkRequestEvent({
required String talkObjectName,
required String lockPeerID,
}) async {
// //
_handleSendExpect(); _handleRequestSendExpect(lockPeerID: lockPeerID);
// //
//test:使 //test:使
playRingtone(); playRingtone();
// //
_showTalkRequestNotification(talkObjectName: talkObjectName); // _showTalkRequestNotification(talkObjectName: talkObjectName);
// //
talkStatus.setPassiveCallWaitingAnswer(); talkStatus.setPassiveCallWaitingAnswer();
//
//
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var isWifiLockType = currentKeyInfo.lockFeature?.wifiLockType == 1;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerID) {
isWifiLockType = lockInfo.lockFeature?.wifiLockType == 1;
}
}
}
}
});
}
if (isWifiLockType) {
Get.toNamed(Routers.imageTransmissionView);
return;
}
if (startChartManage if (startChartManage
.getDefaultTalkExpect() .getDefaultTalkExpect()
.videoType .videoType
.indexOf(VideoTypeE.H264) == .contains(VideoTypeE.H264)) {
-1) {
Get.toNamed( Get.toNamed(
Routers.starChartTalkView, Routers.h264WebView,
); );
} else { } else {
Get.toNamed( Get.toNamed(
Routers.h264WebView, Routers.starChartTalkView,
); );
} }
} }
@ -188,8 +205,97 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
} }
} }
void _handleSendExpect() { /// app收到的对讲请求后
// void _handleRequestSendExpect({
startChartManage.sendOnlyImageVideoTalkExpectData(); required String lockPeerID,
}) async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerID) {
isH264 = lockInfo.lockFeature?.isH264 == 1;
isMJpeg = lockInfo.lockFeature?.isMJpeg == 1;
}
}
}
}
});
}
// 使H264MJPEG
if (isH264) {
// H264H264视频和G711音频期望
startChartManage.sendOnlyH264VideoTalkExpectData();
print(
'app收到的对讲请求后发送的预期数据=========锁支持H264发送H264视频格式期望数据,peerID=${lockPeerID}');
} else if (isMJpeg) {
// MJPEGG711音频期望
startChartManage.sendOnlyImageVideoTalkExpectData();
print(
'app收到的对讲请求后发送的预期数据=========锁不支持H264支持MJPEG发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
} else {
// 使
startChartManage.sendOnlyImageVideoTalkExpectData();
print(
'app收到的对讲请求后发送的预期数据=========锁不支持H264和MJPEG默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
}
}
/// app主动发请求
void _handleResponseSendExpect({
required String lockPeerID,
}) async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var isH264 = currentKeyInfo.lockFeature?.isH264 == 1;
var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerID) {
isH264 = lockInfo.lockFeature?.isH264 == 1;
isMJpeg = lockInfo.lockFeature?.isMJpeg == 1;
}
}
}
}
});
}
// 使H264MJPEG
if (isH264) {
// H264H264视频和G711音频期望
startChartManage.sendH264VideoAndG711AudioTalkExpectData();
AppLog.log(
'app主动发对讲请求收到回复后发送的预期数据=======锁支持H264发送H264视频格式期望数据,peerID=${lockPeerID}');
} else if (isMJpeg) {
// MJPEGG711音频期望
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
AppLog.log(
'app主动发对讲请求收到回复后发送的预期数据=======锁不支持H264支持MJPEG发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
} else {
// 使
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
AppLog.log(
'app主动发对讲请求收到回复后发送的预期数据=======锁不支持H264和MJPEG默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}');
}
} }
} }

View File

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

View File

@ -10,6 +10,10 @@ class PacketLossStatistics {
// key: messageId, value: {totalPackets, receivedPackets} // key: messageId, value: {totalPackets, receivedPackets}
final Map<int, PacketInfo> _packetsMap = HashMap(); final Map<int, PacketInfo> _packetsMap = HashMap();
//
int _maxCapacity = 300; // 300
int _timeoutMs = 30000; // 30
// //
int _totalMessages = 0; // int _totalMessages = 0; //
int _lostMessages = 0; // int _lostMessages = 0; //
@ -18,10 +22,19 @@ class PacketLossStatistics {
// //
void recordPacket(int messageId, int currentIndex, int totalPackets) { void recordPacket(int messageId, int currentIndex, int totalPackets) {
//
_cleanupExpiredPackets();
//
_checkCapacityLimit();
if (!_packetsMap.containsKey(messageId)) { if (!_packetsMap.containsKey(messageId)) {
_packetsMap[messageId] = PacketInfo(totalPackets); _packetsMap[messageId] = PacketInfo(totalPackets);
_totalMessages++; _totalMessages++;
_totalPackets += totalPackets; _totalPackets += totalPackets;
} else {
//
_packetsMap[messageId]!.timestamp = DateTime.now().millisecondsSinceEpoch;
} }
_packetsMap[messageId]!.receivedPackets.add(currentIndex); _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) { void _checkPacketLoss(int messageId) {
final info = _packetsMap[messageId]!; final info = _packetsMap[messageId]!;
@ -62,6 +120,28 @@ class PacketLossStatistics {
return PacketLossInfo(messageLossRate, packetLossRate); 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() { void reset() {
_packetsMap.clear(); _packetsMap.clear();
@ -76,8 +156,10 @@ class PacketLossStatistics {
class PacketInfo { class PacketInfo {
final int totalPackets; final int totalPackets;
final Set<int> receivedPackets = HashSet<int>(); 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 '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'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
class TalkDataRepository { class TalkDataRepository {
TalkDataRepository._() { TalkDataRepository._() {
_talkDataStreamController = StreamController<TalkData>.broadcast( _talkDataStreamController = StreamController<TalkDataModel>.broadcast(
onListen: () { onListen: () {
_isListening = true; _isListening = true;
}, },
@ -18,13 +19,13 @@ class TalkDataRepository {
static TalkDataRepository get instance => _instance; static TalkDataRepository get instance => _instance;
late final StreamController<TalkData> _talkDataStreamController; late final StreamController<TalkDataModel> _talkDataStreamController;
bool _isListening = false; 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) { if (_isListening) {
_talkDataStreamController.add(talkData); _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/constant/udp_constant.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.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/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/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.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 = final H264FrameHandler frameHandler =
H264FrameHandler(onCompleteFrame: (frameData) { H264FrameHandler(onCompleteFrame: (TalkDataModel talkDataModel) {
// //
TalkDataRepository.instance.addTalkData( TalkDataRepository.instance.addTalkData(
TalkData(contentType: TalkData_ContentTypeE.H264, content: frameData), talkDataModel,
); );
}); });
@ -71,6 +72,7 @@ class ScpMessageBaseHandle {
messageId: scpMessage.MessageId!, messageId: scpMessage.MessageId!,
); );
} }
// //
void replyErrorMessage(ScpMessage scpMessage) { void replyErrorMessage(ScpMessage scpMessage) {
startChartManage.sendGenericRespErrorMessage( startChartManage.sendGenericRespErrorMessage(

View File

@ -19,12 +19,14 @@ class VideoTypeE extends $pb.ProtobufEnum {
static const VideoTypeE H264 = VideoTypeE._(1, _omitEnumNames ? '' : 'H264'); static const VideoTypeE H264 = VideoTypeE._(1, _omitEnumNames ? '' : 'H264');
static const VideoTypeE IMAGE = VideoTypeE._(2, _omitEnumNames ? '' : 'IMAGE'); static const VideoTypeE IMAGE = VideoTypeE._(2, _omitEnumNames ? '' : 'IMAGE');
static const VideoTypeE VP8 = VideoTypeE._(3, _omitEnumNames ? '' : 'VP8'); 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> [ static const $core.List<VideoTypeE> values = <VideoTypeE> [
NONE_V, NONE_V,
H264, H264,
IMAGE, IMAGE,
VP8, VP8,
H264_720P,
]; ];
static final $core.Map<$core.int, VideoTypeE> _byValue = $pb.ProtobufEnum.initByValue(values); 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/proto/talk_expect.pbserver.dart';
import 'package:star_lock/talk/starChart/status/star_chart_talk_status.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/baseGetXController.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/deviceInfo_utils.dart'; import 'package:star_lock/tools/deviceInfo_utils.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -112,8 +113,12 @@ class StartChartManage {
RbcuConfirm? rbcuConfirm; RbcuConfirm? rbcuConfirm;
final int _maxPayloadSize = 8 * 1024; // final int _maxPayloadSize = 8 * 1024; //
int rotateAngle = 0; //
int videoWidth = 0; //
int videoHeight = 0; //
// //
TalkExpectReq _defaultTalkExpect = TalkConstant.ImageExpect; TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect;
String relayPeerId = ''; // peerId String relayPeerId = ''; // peerId
@ -227,20 +232,6 @@ class StartChartManage {
/// ///
_onReceiveData(_udpSocket!, Get.context!); _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) { }).catchError((error) {
_log(text: 'Failed to bind UDP socket: $error'); _log(text: 'Failed to bind UDP socket: $error');
}); });
@ -419,17 +410,36 @@ class StartChartManage {
/// ///
void startCallRequestMessageTimer({required String ToPeerId}) async { void startCallRequestMessageTimer({required String ToPeerId}) async {
// //
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) { // if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
// h264则跳转至webview // // h264则跳转至webview
if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) { // if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) {
Get.toNamed( // Get.toNamed(
Routers.h264WebView, // Routers.h264WebView,
); // );
} else { // } else {
Get.toNamed( // Get.toNamed(
Routers.starChartTalkView, // 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( talkRequestTimer ??= Timer.periodic(
@ -596,7 +606,7 @@ class StartChartManage {
void startTalkRejectMessageTimer() async { void startTalkRejectMessageTimer() async {
try { try {
int count = 0; int count = 0;
final int maxCount = 10; // 10 final int maxCount = 3; // 10
talkRejectTimer ??= Timer.periodic( talkRejectTimer ??= Timer.periodic(
Duration(seconds: _defaultIntervalTime), Duration(seconds: _defaultIntervalTime),
@ -622,6 +632,8 @@ class StartChartManage {
stopCallRequestMessageTimer(); stopCallRequestMessageTimer();
stopSendingRbcuInfoMessages(); stopSendingRbcuInfoMessages();
stopSendingRbcuProBeMessages(); stopSendingRbcuProBeMessages();
stopTalkAcceptTimer();
stopCallRequestMessageTimer();
// //
talkePingOverTimeTimerManager.cancel(); talkePingOverTimeTimerManager.cancel();
@ -720,6 +732,8 @@ class StartChartManage {
stopCallRequestMessageTimer(); stopCallRequestMessageTimer();
stopSendingRbcuInfoMessages(); stopSendingRbcuInfoMessages();
stopSendingRbcuProBeMessages(); stopSendingRbcuProBeMessages();
stopTalkAcceptTimer();
stopCallRequestMessageTimer();
// //
talkePingOverTimeTimerManager.cancel(); talkePingOverTimeTimerManager.cancel();
talkDataOverTimeTimerManager.cancel(); talkDataOverTimeTimerManager.cancel();
@ -1145,7 +1159,7 @@ class StartChartManage {
} }
void reSetDefaultTalkExpect() { void reSetDefaultTalkExpect() {
_defaultTalkExpect = TalkConstant.ImageExpect; _defaultTalkExpect = TalkConstant.H264Expect;
} }
TalkExpectReq getDefaultTalkExpect() { TalkExpectReq getDefaultTalkExpect() {
@ -1163,12 +1177,27 @@ class StartChartManage {
} }
/// ///
void sendImageVideoAndG711AudioTalkExpectData() { void sendOnlyH264VideoTalkExpectData() {
final talkExpectReq = TalkConstant.ImageExpect; final talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.H264],
audioType: [],
);
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq); talkExpect: talkExpectReq);
} }
///
void sendImageVideoAndG711AudioTalkExpectData() {
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: TalkConstant.ImageExpect);
}
///
void sendH264VideoAndG711AudioTalkExpectData() {
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: TalkConstant.H264Expect);
}
/// ///
void sendRemoteUnLockMessage({ void sendRemoteUnLockMessage({
required String bluetoothDeviceName, required String bluetoothDeviceName,
@ -1198,6 +1227,16 @@ class StartChartManage {
/// ///
void destruction() async { 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; isOnlineStarChartServer = false;
// //
stopHeartbeat(); stopHeartbeat();
@ -1225,7 +1264,6 @@ class StartChartManage {
await Storage.removerStarChartRegisterNodeInfo(); await Storage.removerStarChartRegisterNodeInfo();
// udp服务 // udp服务
closeUdpSocket(); closeUdpSocket();
PacketLossStatistics().reset();
} }
/// ///

View File

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

View File

@ -0,0 +1,675 @@
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';
import 'package:flutter/services.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:get/get.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/login/login/entity/LoginEntity.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
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';
import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_state.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
import 'package:star_lock/tools/G711Tool.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/storage.dart';
import '../../../../tools/baseGetXController.dart';
class ImageTransmissionLogic extends BaseGetXController {
ImageTransmissionState state = ImageTransmissionState();
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
int bufferSize = 8; //
int audioBufferSize = 2; // 2
bool _isFirstAudioFrame = true; //
int _startAudioTime = 0; //
//
final List<int> _bufferedAudioFrames = <int>[];
//
bool _isListening = false;
StreamSubscription? _streamSubscription;
Timer? videoRenderTimer; //
int _renderedFrameCount = 0;
int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch;
///
void _initFlutterPcmSound() {
const int sampleRate = 8000;
FlutterPcmSound.setLogLevel(LogLevel.none);
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
// feed
if (Platform.isAndroid) {
FlutterPcmSound.setFeedThreshold(1024); // Android
} else {
FlutterPcmSound.setFeedThreshold(2000); // Android
}
}
///
void udpHangUpAction() async {
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
//
StartChartManage().startTalkHangupMessageTimer();
} else {
//
StartChartManage().startTalkRejectMessageTimer();
}
Get.back();
}
//
void initiateAnswerCommand() {
StartChartManage().startTalkAcceptTimer();
}
//
void _startListenTalkData() {
//
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;
}
//
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.Image:
// bufferSize帧
state.videoBuffer.add(talkData);
if (state.videoBuffer.length > bufferSize) {
state.videoBuffer.removeAt(0); //
}
break;
}
});
}
//
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 _startListenTalkStatus() {
state.startChartTalkStatus.statusStream.listen((talkStatus) {
state.talkStatus.value = talkStatus;
switch (talkStatus) {
case TalkStatus.rejected:
case TalkStatus.hangingUpDuring:
case TalkStatus.notTalkData:
case TalkStatus.notTalkPing:
case TalkStatus.end:
_handleInvalidTalkStatus();
break;
case TalkStatus.answeredSuccessfully:
state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer ??=
Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (state.listData.value.length > 0) {
state.oneMinuteTime.value++;
// if (state.oneMinuteTime.value >= 60) {
// t.cancel(); //
// state.oneMinuteTime.value = 0;
// //
// // udpHangUpAction();
// }
}
});
break;
default:
//
break;
}
});
}
///
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;
}
}
}
///
void _stopPlayG711Data() async {
await FlutterPcmSound.pause();
await FlutterPcmSound.stop();
await FlutterPcmSound.clear();
}
///
// udpOpenDoorAction() async {
// final List<String>? privateKey =
// await Storage.getStringList(saveBluePrivateKey);
// final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
//
// final List<String>? signKey = await Storage.getStringList(saveBlueSignKey);
// final List<int> signKeyDataList = changeStringListToIntList(signKey!);
//
// final List<String>? token = await Storage.getStringList(saveBlueToken);
// final List<int> getTokenList = changeStringListToIntList(token!);
//
// await _getLockNetToken();
//
// final OpenLockCommand openLockCommand = OpenLockCommand(
// lockID: BlueManage().connectDeviceName,
// userID: await Storage.getUid(),
// openMode: lockDetailState.openDoorModel,
// openTime: _getUTCNetTime(),
// onlineToken: lockDetailState.lockNetToken,
// token: getTokenList,
// needAuthor: 1,
// signKey: signKeyDataList,
// privateKey: getPrivateKeyList,
// );
// final messageDetail = openLockCommand.packageData();
// // List<int>
// String hexString = messageDetail
// .map((byte) => byte.toRadixString(16).padLeft(2, '0'))
// .join(' ');
//
// AppLog.log('open lock hexString: $hexString');
// //
// StartChartManage().sendRemoteUnLockMessage(
// bluetoothDeviceName: BlueManage().connectDeviceName,
// openLockCommand: messageDetail,
// );
// showToast('正在开锁中...'.tr);
// }
int _getUTCNetTime() {
if (lockDetailState.isHaveNetwork) {
return DateTime.now().millisecondsSinceEpoch ~/ 1000 +
lockDetailState.differentialTime;
} else {
return 0;
}
}
///
Future<bool> getPermissionStatus() async {
final Permission permission = Permission.microphone;
//granted denied permanentlyDenied
final PermissionStatus status = await permission.status;
if (status.isGranted) {
return true;
} else if (status.isDenied) {
requestPermission(permission);
} else if (status.isPermanentlyDenied) {
openAppSettings();
} else if (status.isRestricted) {
requestPermission(permission);
} else {}
return false;
}
///
void requestPermission(Permission permission) async {
final PermissionStatus status = await permission.request();
if (status.isPermanentlyDenied) {
openAppSettings();
}
}
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(); //
}
}
}
Future<void> startRecording() async {
// requestPermissions();
// if (state.isRecordingScreen.value) {
// showToast('录屏已开始,请勿重复点击');
// }
// bool start = await FlutterScreenRecording.startRecordScreen(
// "Screen Recording", //
// titleNotification: "Recording in progress", //
// messageNotification: "Tap to stop recording", //
// );
//
// if (start) {
// state.isRecordingScreen.value = true;
// }
}
Future<void> stopRecording() async {
// String path = await FlutterScreenRecording.stopRecordScreen;
// print("Recording saved to: $path");
//
// //
// bool? success = await GallerySaver.saveVideo(path);
// if (success == true) {
// print("Video saved to gallery");
// } else {
// print("Failed to save video to gallery");
// }
//
// showToast('录屏结束,已保存到系统相册');
// state.isRecordingScreen.value = false;
}
@override
void onReady() {
super.onReady();
}
@override
void onInit() {
super.onInit();
//
_startListenTalkData();
//
_startListenTalkStatus();
//
// *** ***
state.talkStatus.value = state.startChartTalkStatus.status;
//
_initFlutterPcmSound();
//
// _startPlayback();
//
_initAudioRecorder();
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
void onClose() {
_stopPlayG711Data(); //
state.listData.value = Uint8List(0); //
state.audioBuffer.clear(); //
state.videoBuffer.clear(); //
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
stopProcessingAudio();
//
// _imageCache.clear();
state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer = null; //
state.oneMinuteTime.value = 0;
//
_streamSubscription?.cancel();
_isListening = false;
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.onClose();
}
@override
void dispose() {
stopProcessingAudio();
//
StartChartManage().reSetDefaultTalkExpect();
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.dispose();
}
///
void _handleInvalidTalkStatus() {
state.listData.value = Uint8List(0);
//
_stopPlayG711Data();
stopProcessingAudio();
}
///
void updateTalkExpect() {
TalkExpectReq talkExpectReq = TalkExpectReq();
state.isOpenVoice.value = !state.isOpenVoice.value;
if (!state.isOpenVoice.value) {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
audioType: [],
);
showToast('已静音'.tr);
} else {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
audioType: [AudioTypeE.G711],
);
}
///
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq);
}
///
Future<void> captureAndSavePng() async {
try {
if (state.globalKey.currentContext == null) {
AppLog.log('截图失败: 未找到当前上下文');
return;
}
final RenderRepaintBoundary boundary = state.globalKey.currentContext!
.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
AppLog.log('截图失败: 图像数据为空');
return;
}
final Uint8List pngBytes = byteData.buffer.asUint8List();
//
final Directory directory = await getApplicationDocumentsDirectory();
final String imagePath = '${directory.path}/screenshot.png';
//
final File imgFile = File(imagePath);
await imgFile.writeAsBytes(pngBytes);
//
await ImageGallerySaver.saveFile(imagePath);
AppLog.log('截图保存路径: $imagePath');
showToast('截图已保存到相册'.tr);
} catch (e) {
AppLog.log('截图失败: $e');
}
}
//
Future<void> remoteOpenLock() async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var lockId = currentKeyInfo.lockId ?? 0;
var remoteUnlock = currentKeyInfo.lockSetting?.remoteUnlock ?? 0;
final lockPeerId = StartChartManage().lockPeerId;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerId) {
lockId = lockInfo.lockId ?? 0;
remoteUnlock = lockInfo.lockSetting?.remoteUnlock ?? 0;
}
}
}
}
});
}
if (remoteUnlock == 1) {
final LoginEntity entity = await ApiRepository.to
.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
if (entity.errorCode!.codeIsSuccessful) {
showToast('已开锁'.tr);
StartChartManage().lockListPeerId = [];
}
} else {
showToast('该锁的远程开锁功能未启用'.tr);
}
}
///
void _initAudioRecorder() {
state.voiceProcessor = VoiceProcessor.instance;
}
//
Future<void> startProcessingAudio() async {
try {
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!;
state.startRecordingAudioTime.value = DateTime.now();
//
state.voiceProcessor
?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
state.voiceProcessor?.addErrorListener(_onError);
} else {
// state.errorMessage.value = 'Recording permission not granted';
}
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex';
}
state.isOpenVoice.value = false;
}
///
Future<void> stopProcessingAudio() async {
try {
await state.voiceProcessor?.stop();
state.voiceProcessor?.removeFrameListener(_onFrame);
state.udpSendDataFrameNumber = 0;
//
state.endRecordingAudioTime.value = DateTime.now();
//
final Duration duration = state.endRecordingAudioTime.value
.difference(state.startRecordingAudioTime.value);
state.recordingAudioTime.value = duration.inSeconds;
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally {
final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!;
state.isOpenVoice.value = true;
}
}
//
Future<void> _onFrame(List<int> frame) async {
//
if (_bufferedAudioFrames.length > state.frameLength * 3) {
_bufferedAudioFrames.clear(); //
return;
}
//
List<int> amplifiedFrame = _applyGain(frame, 1.6);
// G711数据
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData);
// 使
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使
int getFrameLength = state.frameLength;
if (Platform.isIOS) {
getFrameLength = state.frameLength * 2;
}
//
if (_bufferedAudioFrames.length >= state.frameLength) {
try {
await StartChartManage().sendTalkDataMessage(
talkData: TalkData(
content: _bufferedAudioFrames,
contentType: TalkData_ContentTypeE.G711,
durationMs: ms,
),
);
} finally {
_bufferedAudioFrames.clear(); //
}
} else {
_bufferedAudioFrames.addAll(encodedData);
}
}
//
void _onError(VoiceProcessorException error) {
AppLog.log(error.message!);
}
//
List<int> _applyGain(List<int> pcmData, double gainFactor) {
List<int> result = List<int>.filled(pcmData.length, 0);
for (int i = 0; i < pcmData.length; i++) {
// PCM数据通常是有符号的16位整数
int sample = pcmData[i];
//
double amplified = sample * gainFactor;
//
if (amplified > 32767) {
amplified = 32767;
} else if (amplified < -32768) {
amplified = -32768;
}
result[i] = amplified.toInt();
}
return result;
}
}

View File

@ -0,0 +1,238 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_colors.dart';
import 'package:star_lock/talk/call/callTalk.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_logic.dart';
import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_state.dart';
import 'package:star_lock/tools/titleAppBar.dart';
import 'package:slide_to_act/slide_to_act.dart';
//
// import 'package:flutter_slider_button/flutter_slider_button.dart';
class ImageTransmissionPage extends StatefulWidget {
const ImageTransmissionPage();
@override
State<ImageTransmissionPage> createState() => _ImageTransmissionPageState();
}
class _ImageTransmissionPageState extends State<ImageTransmissionPage>
with TickerProviderStateMixin {
final ImageTransmissionLogic logic = Get.put(ImageTransmissionLogic());
final ImageTransmissionState state = Get.find<ImageTransmissionLogic>().state;
final startChartManage = StartChartManage();
@override
void initState() {
super.initState();
state.animationController = AnimationController(
vsync: this, // 使TickerProvider是当前Widget
duration: const Duration(seconds: 1),
);
state.animationController.repeat();
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
void dispose() {
state.animationController.dispose();
CallTalk().finishAVData();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBackgroundColor,
resizeToAvoidBottomInset: false,
appBar: TitleAppBar(
barTitle: '图传'.tr,
haveBack: true,
backgroundColor: AppColors.mainColor,
backAction: () {
logic.udpHangUpAction();
},
),
body: Obx(() => Column(
children: [
SizedBox(height: 24.h),
SizedBox(
height: 0.6.sh,
child: state.listData.value.isEmpty
? _buildWaitingView()
: _buildVideoView(),
),
SizedBox(height: 30.h),
_buildBottomToolBar(),
SizedBox(height: 30.h),
],
)),
);
}
Widget _buildWaitingView() {
double barWidth = MediaQuery.of(context).size.width - 60.w;
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(30.h),
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: barWidth,
height: double.infinity,
child: Image.asset(
'images/main/monitorBg.png',
fit: BoxFit.cover,
),
),
RotationTransition(
turns: state.animationController,
child: Image.asset(
'images/main/realTime_connecting.png',
width: 300.w,
height: 300.w,
fit: BoxFit.contain,
),
),
],
),
),
);
}
Widget _buildVideoView() {
double barWidth = MediaQuery.of(context).size.width - 60.w;
return PopScope(
canPop: false,
child: RepaintBoundary(
key: state.globalKey,
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(30.h),
child: Container(
width: barWidth,
height: double.infinity,
child: RotatedBox(
quarterTurns: startChartManage.rotateAngle ~/ 90,
child: RawImage(
image: state.currentImage.value,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
),
),
),
),
),
),
);
}
Widget _buildBottomToolBar() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 30.w),
padding: EdgeInsets.symmetric(vertical: 28.h, horizontal: 20.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30.h),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 12,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_circleButton(
icon: Icons.call,
color: Colors.green,
onTap: () {
if (state.talkStatus.value ==
TalkStatus.passiveCallWaitingAnswer) {
//
logic.initiateAnswerCommand();
}
},
),
_circleButton(
icon: Icons.call_end,
color: Colors.red,
onTap: () {
logic.udpHangUpAction();
},
),
_circleButton(
icon: Icons.camera_alt,
color: Colors.blue,
onTap: () async {
await logic.captureAndSavePng();
},
),
],
),
SizedBox(height: 36.h),
SlideAction(
height: 64.h,
borderRadius: 24.h,
elevation: 0,
innerColor: Colors.amber,
outerColor: Colors.amber.withOpacity(0.15),
sliderButtonIcon: Icon(Icons.lock, color: Colors.white, size: 40.w),
text: '滑动解锁',
textStyle: TextStyle(
fontSize: 26.sp,
color: Colors.black54,
fontWeight: FontWeight.bold),
onSubmit: () {
// TODO:
logic.remoteOpenLock();
},
),
],
),
);
}
Widget _circleButton(
{required IconData icon,
required Color color,
required VoidCallback onTap}) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 90.w,
height: 90.w,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: color.withOpacity(0.3),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Icon(icon, color: Colors.white, size: 48.w),
),
);
}
}

View File

@ -0,0 +1,94 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
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/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 '../../../../tools/storage.dart';
enum NetworkStatus {
normal, // 0
lagging, // 1
delayed, // 2
packetLoss // 3
}
class ImageTransmissionState{
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();
Rx<Uint8List> listData = Uint8List(0).obs; //
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;
late Timer autoBackTimer =
Timer(const Duration(seconds: 1), () {}); //30
late Timer realTimePicTimer =
Timer(const Duration(seconds: 1), () {}); //
RxInt elapsedSeconds = 0.obs;
//
List<TalkData> audioBuffer = <TalkData>[].obs;
List<TalkData> activeAudioBuffer = <TalkData>[].obs;
List<TalkData> activeVideoBuffer = <TalkData>[].obs;
List<TalkData> videoBuffer = <TalkData>[].obs;
List<TalkData> videoBuffer2 = <TalkData>[].obs;
RxBool isPlaying = false.obs; //
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //
// startChartTalkStatus
final StartChartTalkStatus startChartTalkStatus =
StartChartTalkStatus.instance;
//
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
RxInt lastFrameTimestamp = 0.obs; // ,
Rx<NetworkStatus> networkStatus =
NetworkStatus.normal.obs; // 0- 1- 2- 3-
RxInt alertCount = 0.obs; //
RxInt maxAlertNumber = 3.obs; //
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; //
RxInt fps = 0.obs; // FPS
late VoiceProcessor? voiceProcessor; //
final int frameLength = 320; //640
final int sampleRate = 8000; //8000
List<int> recordingAudioAllFrames = <int>[]; //
List<int> lockRecordingAudioAllFrames = <int>[]; //
RxInt rotateAngle = 0.obs; //
RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,534 @@
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); //
// loading中或textureId为nullloading/
if (state.isLoading.isTrue || state.textureId.value == null) {
return Image.asset(
'images/main/monitorBg.png',
width: screenWidth,
height: screenHeight,
fit: BoxFit.cover,
);
} else {
return 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'),
),
),
),
SizedBox(width: 50.w),
//
GestureDetector(
onTap: () async {
//
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20.w)),
),
builder: (BuildContext context) {
final List<String> qualities = ['高清', '标清'];
return SafeArea(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: qualities.map((q) {
return Obx(() => InkWell(
onTap: () {
Navigator.of(context).pop();
logic.onQualityChanged(q);
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 18.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Text(
q,
style: TextStyle(
color: state.currentQuality.value == q
? AppColors.mainColor
: Colors.black,
fontWeight: state.currentQuality.value == q
? FontWeight.bold
: FontWeight.normal,
fontSize: 28.sp,
),
),
],
),
),
));
}).toList(),
),
),
);
},
);
},
child: Container(
child: Icon(Icons.high_quality_outlined, color: Colors.white, size: 38.w),
),
),
]);
}
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,123 @@
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 = 15; //
final int targetFps = 30; // ,native的缓冲区
Timer? frameProcessTimer; //
bool isProcessingFrame = false; //
int lastProcessedTimestamp = 0; //
// H264文件保存相关
String? h264FilePath;
File? h264File;
// '高清'
RxString currentQuality = '高清'.obs; //
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:math'; // Import the math package to use sqrt import 'dart:math'; // Import the math package to use sqrt
import 'dart:ui' show decodeImageFromList;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -23,12 +24,15 @@ import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/g711.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/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_data.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.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/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
import 'package:star_lock/tools/G711Tool.dart'; import 'package:star_lock/tools/G711Tool.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/storage.dart';
import '../../../../tools/baseGetXController.dart'; import '../../../../tools/baseGetXController.dart';
@ -37,32 +41,24 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
final int minBufferSize = 2; // 2166ms int bufferSize = 8; //
final int maxBufferSize = 20; // 8666ms
int bufferSize = 8; //
//
final int minAudioBufferSize = 1; // 1
final int maxAudioBufferSize = 3; // 3
int audioBufferSize = 2; // 2
// int audioBufferSize = 2; // 2
int _startTime = 0; //
int _startAudioTime = 0; //
bool _isFirstFrame = true; //
bool _isFirstAudioFrame = true; // bool _isFirstAudioFrame = true; //
int _startAudioTime = 0; //
// //
final List<int> _bufferedAudioFrames = <int>[]; final List<int> _bufferedAudioFrames = <int>[];
final Map<String, ui.Image> _imageCache = {}; //
bool _isListening = false;
StreamSubscription? _streamSubscription;
// Timer? videoRenderTimer; //
int _lastFrameTimestamp = 0; // 0
// int _renderedFrameCount = 0;
int _frameCount = 0; int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch;
int _lastFpsUpdateTime = 0;
Timer? _fpsTimer;
/// ///
void _initFlutterPcmSound() { void _initFlutterPcmSound() {
@ -96,18 +92,28 @@ class TalkViewLogic extends BaseGetXController {
// //
void _startListenTalkData() { 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; final currentTime = DateTime.now().millisecondsSinceEpoch;
// //
switch (contentType) { switch (contentType) {
case TalkData_ContentTypeE.G711: case TalkData_ContentTypeE.G711:
// // // //
// if (_isFirstAudioFrame) { if (_isFirstAudioFrame) {
// _startAudioTime = currentTime; _startAudioTime = currentTime;
// _isFirstAudioFrame = false; _isFirstAudioFrame = false;
// } }
// //
final expectedTime = _startAudioTime + talkData.durationMs; final expectedTime = _startAudioTime + talkData.durationMs;
@ -129,80 +135,16 @@ class TalkViewLogic extends BaseGetXController {
_playAudioFrames(); _playAudioFrames();
break; break;
case TalkData_ContentTypeE.Image: case TalkData_ContentTypeE.Image:
// // bufferSize帧
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);
}
state.videoBuffer.add(talkData); state.videoBuffer.add(talkData);
// if (state.videoBuffer.length > bufferSize) {
await _decodeAndCacheFrame(talkData); state.videoBuffer.removeAt(0); //
// }
_playVideoFrames();
break; 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() { void _playAudioFrames() {
// //
@ -233,50 +175,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() { void _startListenTalkStatus() {
state.startChartTalkStatus.statusStream.listen((talkStatus) { state.startChartTalkStatus.statusStream.listen((talkStatus) {
@ -382,20 +280,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 { Future<bool> getPermissionStatus() async {
final Permission permission = Permission.microphone; final Permission permission = Permission.microphone;
@ -498,6 +382,31 @@ class TalkViewLogic extends BaseGetXController {
requestPermissions(); 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 @override
@ -512,10 +421,17 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio(); stopProcessingAudio();
// //
_imageCache.clear(); // _imageCache.clear();
state.oneMinuteTimeTimer?.cancel(); // state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer = null; // state.oneMinuteTimeTimer = null; //
state.oneMinuteTime.value = 0; state.oneMinuteTime.value = 0;
//
_streamSubscription?.cancel();
_isListening = false;
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.onClose(); super.onClose();
} }
@ -525,6 +441,9 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio(); stopProcessingAudio();
// //
StartChartManage().reSetDefaultTalkExpect(); StartChartManage().reSetDefaultTalkExpect();
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.dispose(); super.dispose();
} }
@ -597,34 +516,40 @@ class TalkViewLogic extends BaseGetXController {
// //
Future<void> remoteOpenLock() async { Future<void> remoteOpenLock() async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var lockId = currentKeyInfo.lockId ?? 0;
var remoteUnlock = currentKeyInfo.lockSetting?.remoteUnlock ?? 0;
final lockPeerId = StartChartManage().lockPeerId; final lockPeerId = StartChartManage().lockPeerId;
final lockListPeerId = StartChartManage().lockListPeerId; final LockListInfoGroupEntity? lockListInfoGroupEntity =
int lockId = lockDetailState.keyInfos.value.lockId ?? 0; await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
// peerId使peerId lockListInfoGroupEntity!.groupList?.forEach((element) {
// peerId final lockList = element.lockList;
lockListPeerId.forEach((element) { if (lockList != null && lockList.length != 0) {
if (element.network?.peerId == lockPeerId) { for (var lockInfo in lockList) {
lockId = element.lockId ?? 0; final peerId = lockInfo.network?.peerId;
} if (peerId != null && peerId != '') {
}); if (peerId == lockPeerId) {
lockId = lockInfo.lockId ?? 0;
final LockSetInfoEntity lockSetInfoEntity = remoteUnlock = lockInfo.lockSetting?.remoteUnlock ?? 0;
await ApiRepository.to.getLockSettingInfoData( }
lockId: lockId.toString(), }
); }
if (lockSetInfoEntity.errorCode!.codeIsSuccessful) {
if (lockSetInfoEntity.data?.lockFeature?.remoteUnlock == 1 &&
lockSetInfoEntity.data?.lockSettingInfo?.remoteUnlock == 1) {
final LoginEntity entity = await ApiRepository.to
.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
if (entity.errorCode!.codeIsSuccessful) {
showToast('已开锁'.tr);
StartChartManage().lockListPeerId = [];
} }
} else { });
showToast('该锁的远程开锁功能未启用'.tr); }
if (remoteUnlock == 1) {
final LoginEntity entity = await ApiRepository.to
.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
if (entity.errorCode!.codeIsSuccessful) {
showToast('已开锁'.tr);
StartChartManage().lockListPeerId = [];
} }
} else {
showToast('该锁的远程开锁功能未启用'.tr);
} }
} }

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/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.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/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_logic.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.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 TalkViewLogic logic = Get.put(TalkViewLogic());
final TalkViewState state = Get.find<TalkViewLogic>().state; final TalkViewState state = Get.find<TalkViewLogic>().state;
late Stream<int> _latencyStream; late Stream<int> _latencyStream;
final startChartManage = StartChartManage();
@override @override
void initState() { void initState() {
@ -96,60 +98,55 @@ class _TalkViewPageState extends State<TalkViewPage>
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ children: <Widget>[
Obx( //
() { Obx(() {
final double screenWidth = MediaQuery.of(context).size.width; if (state.listData.value.isEmpty) {
final double screenHeight = MediaQuery.of(context).size.height; return SizedBox.expand(
child: Image.asset(
final double logicalWidth = MediaQuery.of(context).size.width; 'images/main/monitorBg.png',
final double logicalHeight = MediaQuery.of(context).size.height; fit: BoxFit.cover,
final double devicePixelRatio = ),
MediaQuery.of(context).devicePixelRatio; );
}
// final int videoW = startChartManage.videoWidth;
final double physicalWidth = logicalWidth * devicePixelRatio; final int videoH = startChartManage.videoHeight;
final double physicalHeight = logicalHeight * devicePixelRatio; if (videoW == 320 && videoH == 240) {
return SizedBox.expand(
// child: Container(
const int rotatedImageWidth = 480; // decoration: const BoxDecoration(
const int rotatedImageHeight = 864; // gradient: LinearGradient(
begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
final double scaleWidth = physicalWidth / rotatedImageWidth; colors: [
final double scaleHeight = physicalHeight / rotatedImageHeight; Color(0xFF232526),
max(scaleWidth, scaleHeight); // Color(0xFF414345),
],
return state.listData.value.isEmpty ),
? Image.asset( ),
'images/main/monitorBg.png', ),
width: screenWidth, );
height: screenHeight, }
fit: BoxFit.cover, return const SizedBox.shrink();
) }),
: PopScope( //
canPop: false, Obx(() {
child: RepaintBoundary( if (state.listData.value.isEmpty) {
key: state.globalKey, return const SizedBox.shrink();
child: SizedBox.expand( }
child: RotatedBox( final int videoW = startChartManage.videoWidth;
quarterTurns: -1, final int videoH = startChartManage.videoHeight;
child: Obx( if (videoW == 320 && videoH == 240) {
() => state.currentImage.value != null return Positioned(
? RawImage( top: 150.h,
image: state.currentImage.value, left: 0,
width: ScreenUtil().scaleWidth, right: 0,
height: ScreenUtil().scaleHeight, child: _buildVideoWidget(),
fit: BoxFit.cover, );
filterQuality: FilterQuality.high, } else {
) //
: Container(color: Colors.transparent), return _buildVideoWidget();
), }
), }),
),
),
);
},
),
Obx(() => state.listData.value.isEmpty Obx(() => state.listData.value.isEmpty
? Positioned( ? Positioned(
bottom: 310.h, bottom: 310.h,
@ -158,35 +155,35 @@ class _TalkViewPageState extends State<TalkViewPage>
style: TextStyle(color: Colors.black, fontSize: 26.sp), style: TextStyle(color: Colors.black, fontSize: 26.sp),
)) ))
: Container()), : Container()),
Obx( Obx(() =>
() => state.listData.value.isNotEmpty && state.listData.value.isNotEmpty && state.oneMinuteTime.value > 0
state.oneMinuteTime.value > 0 ? Positioned(
? Positioned( top: ScreenUtil().statusBarHeight + 75.h,
top: ScreenUtil().statusBarHeight + 75.h, width: 1.sw,
width: 1.sw, child: Obx(
child: Obx( () {
() { final String sec = (state.oneMinuteTime.value % 60)
final String sec = (state.oneMinuteTime.value % 60) .toString()
.toString() .padLeft(2, '0');
.padLeft(2, '0'); final String min = (state.oneMinuteTime.value ~/ 60)
final String min = (state.oneMinuteTime.value ~/ 60) .toString()
.toString() .padLeft(2, '0');
.padLeft(2, '0'); return Row(
return Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
children: <Widget>[ Text(
Text( '$min:$sec',
'$min:$sec', style: TextStyle(
style: TextStyle( fontSize: 26.sp, color: Colors.white),
fontSize: 26.sp, color: Colors.white), ),
), ],
], );
); },
}, ),
), )
) : Container()),
: Container(),
), ///
Positioned( Positioned(
bottom: 10.w, bottom: 10.w,
child: Container( child: Container(
@ -456,7 +453,6 @@ class _TalkViewPageState extends State<TalkViewPage>
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully && // if (state.talkStatus.value == TalkStatus.answeredSuccessfully &&
// state.listData.value.length > 0) { // state.listData.value.length > 0) {
// logic.udpOpenDoorAction(); // logic.udpOpenDoorAction();
logic.remoteOpenLock();
// } // }
// if (UDPManage().remoteUnlock == 1) { // if (UDPManage().remoteUnlock == 1) {
// logic.udpOpenDoorAction(); // logic.udpOpenDoorAction();
@ -464,6 +460,7 @@ class _TalkViewPageState extends State<TalkViewPage>
// } else { // } else {
// logic.showToast('请在锁设置中开启远程开锁'.tr); // logic.showToast('请在锁设置中开启远程开锁'.tr);
// } // }
logic.remoteOpenLock();
}, },
) )
]); ]);
@ -618,4 +615,68 @@ class _TalkViewPageState extends State<TalkViewPage>
// UdpTalkDataHandler().resetDataRates(); // UdpTalkDataHandler().resetDataRates();
super.dispose(); super.dispose();
} }
Widget _buildVideoWidget() {
//
double barWidth = 1.sw - 30.w * 2;
int videoW = startChartManage.videoWidth;
int videoH = startChartManage.videoHeight;
int quarterTurns = startChartManage.rotateAngle ~/ 90;
bool isRotated = quarterTurns % 2 == 1;
//
double videoAspect = isRotated ? videoW / videoH : videoH / videoW;
double containerHeight =
barWidth * (isRotated ? videoW / videoH : videoH / videoW);
if (videoW == 320 && videoH == 240) {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(20.h),
child: Container(
width: barWidth,
height: containerHeight,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF232526),
Color(0xFF414345),
],
),
),
child: RotatedBox(
quarterTurns: quarterTurns,
child: RawImage(
image: state.currentImage.value,
fit: BoxFit.contain,
filterQuality: FilterQuality.high,
width: barWidth,
height: containerHeight,
),
),
),
),
);
} else {
return PopScope(
canPop: false,
child: RepaintBoundary(
key: state.globalKey,
child: SizedBox.expand(
child: RotatedBox(
quarterTurns: startChartManage.rotateAngle ~/ 90,
child: RawImage(
image: state.currentImage.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
),
),
),
),
);
}
}
} }

View File

@ -90,6 +90,5 @@ class TalkViewState {
RxBool isLongPressing = false.obs; // RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; // RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; // RxInt lastAudioTimestamp = 0.obs; //
// Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
final 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/call/g711.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.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/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.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/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.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; static const int CHUNK_SIZE = 4096;
Timer? _mockDataTimer; Timer? _mockDataTimer;
int _startAudioTime = 0; //
int audioBufferSize = 2; // 2
bool _isFirstAudioFrame = true; //
// //
final List<int> _bufferedAudioFrames = <int>[]; final List<int> _bufferedAudioFrames = <int>[];
final Queue<List<int>> _frameBuffer = Queue<List<int>>(); final Queue<List<int>> _frameBuffer = Queue<List<int>>();
static const int FRAME_BUFFER_SIZE = 25; static const int FRAME_BUFFER_SIZE = 25;
//
bool _isListening = false;
StreamSubscription? _streamSubscription;
@override @override
void onInit() { void onInit() {
super.onInit();
// WebView // WebView
state.webViewController = WebViewController() state.webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) ..setJavaScriptMode(JavaScriptMode.unrestricted)
@ -63,18 +71,40 @@ class H264WebViewLogic extends BaseGetXController {
}, },
); );
state.isShowLoading.value = true; super.onInit();
// HTML
_loadLocalHtml();
// //
_createFramesStreamListen(); _createFramesStreamListen();
// playLocalTestVideo();
_startListenTalkStatus(); _startListenTalkStatus();
state.talkStatus.value = state.startChartTalkStatus.status; state.talkStatus.value = state.startChartTalkStatus.status;
// //
_initFlutterPcmSound(); _initFlutterPcmSound();
// //
_initAudioRecorder(); _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 { void _createFramesStreamListen() async {
state.talkDataRepository.talkDataStream.listen((TalkData event) async { //
// if (_isListening) {
_frameBuffer.add(event.content); AppLog.log("已经存在数据流监听,避免重复监听");
return;
}
// , AppLog.log("==== 启动新的数据流监听 ====");
while (_frameBuffer.length > FRAME_BUFFER_SIZE) { _isListening = true;
if (_frameBuffer.isNotEmpty) { _streamSubscription = state.talkDataRepository.talkDataStream
final frame = _frameBuffer.removeFirst(); .listen((TalkDataModel talkDataModel) async {
await _sendBufferedData(frame); 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文件 /// html文件
Future<void> _loadLocalHtml() async { Future<void> _loadLocalHtml() async {
// HTML // HTML
@ -186,10 +311,10 @@ class H264WebViewLogic extends BaseGetXController {
Timer.periodic(const Duration(seconds: 1), (Timer t) { Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (state.isShowLoading.isFalse) { if (state.isShowLoading.isFalse) {
state.oneMinuteTime.value++; state.oneMinuteTime.value++;
if (state.oneMinuteTime.value >= 60) { // if (state.oneMinuteTime.value >= 60) {
t.cancel(); // // t.cancel(); //
state.oneMinuteTime.value = 0; // state.oneMinuteTime.value = 0;
} // }
} }
}); });
break; break;
@ -321,7 +446,7 @@ class H264WebViewLogic extends BaseGetXController {
} }
// //
List<int> amplifiedFrame = _applyGain(frame, 1.6); List<int> amplifiedFrame = _applyGain(frame, 1.8);
// G711数据 // G711数据
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData); _bufferedAudioFrames.addAll(encodedData);
@ -409,7 +534,7 @@ class H264WebViewLogic extends BaseGetXController {
}); });
final LockSetInfoEntity lockSetInfoEntity = final LockSetInfoEntity lockSetInfoEntity =
await ApiRepository.to.getLockSettingInfoData( await ApiRepository.to.getLockSettingInfoData(
lockId: lockId.toString(), lockId: lockId.toString(),
); );
if (lockSetInfoEntity.errorCode!.codeIsSuccessful) { 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 @override
void dispose() { void dispose() {

View File

@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_colors.dart'; import 'package:star_lock/app_settings/app_colors.dart';
import 'package:star_lock/app_settings/app_settings.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/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart'; import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart';
@ -25,6 +26,7 @@ class _H264WebViewState extends State<H264WebView>
with TickerProviderStateMixin { with TickerProviderStateMixin {
final H264WebViewLogic logic = Get.put(H264WebViewLogic()); final H264WebViewLogic logic = Get.put(H264WebViewLogic());
final H264WebViewState state = Get.find<H264WebViewLogic>().state; final H264WebViewState state = Get.find<H264WebViewLogic>().state;
final startChartManage = StartChartManage();
@override @override
void initState() { void initState() {
@ -71,8 +73,13 @@ class _H264WebViewState extends State<H264WebView>
height: screenHeight, height: screenHeight,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: WebViewWidget( : SizedBox.expand(
controller: state.webViewController, child: RotatedBox(
quarterTurns: startChartManage.rotateAngle ~/ 90,
child: WebViewWidget(
controller: state.webViewController,
),
),
); );
}), }),
Obx( Obx(
@ -411,10 +418,13 @@ class _H264WebViewState extends State<H264WebView>
), ),
); );
} }
@override @override
void dispose() { void dispose() {
state.animationController.dispose(); // state.animationController.dispose();
super.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:get/get.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.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/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/status/star_chart_talk_status.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
@ -49,4 +50,6 @@ class H264WebViewState {
RxInt rotateAngle = 0.obs; // RxInt rotateAngle = 0.obs; //
RxBool hasAudioData = false.obs; // RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; // RxInt lastAudioTimestamp = 0.obs; //
List<TalkData> audioBuffer = <TalkData>[].obs;
RxBool isPlaying = false.obs; //
} }

View File

@ -1,4 +1,5 @@
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import '../main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; import '../main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
@ -195,6 +196,7 @@ class RogerThatLockInfoDataEvent {
class GetGatewayListRefreshUI { class GetGatewayListRefreshUI {
GetGatewayListRefreshUI(); GetGatewayListRefreshUI();
} }
/// ///
class AgreePrivacyAgreement { class AgreePrivacyAgreement {
AgreePrivacyAgreement(); AgreePrivacyAgreement();
@ -204,3 +206,10 @@ class AgreePrivacyAgreement {
class SuccessfulDistributionNetwork { class SuccessfulDistributionNetwork {
SuccessfulDistributionNetwork(); SuccessfulDistributionNetwork();
} }
///
class SetLockListInfoGroupEntity {
SetLockListInfoGroupEntity({required this.lockListInfoGroupEntity});
LockListInfoGroupEntity lockListInfoGroupEntity;
}

View File

@ -2,13 +2,13 @@
set -e set -e
APP_PRODUCT_NAME=$APP_PRODUCT_NAME APP_PRODUCT_NAME=$APP_PRODUCT_NAME
BUILD_STATUS=$1 BUILD_STATUS=$1
if [[ "${CI_COMMIT_BRANCH}" == "release" ]] ; then if [[ "${CI_COMMIT_BRANCH}" == "release_sky" ]] ; then
WECAHT_WEBHOOK_URL=$PRE_QYWECAHT_WEBHOOK_URL WECAHT_WEBHOOK_URL=$PRE_QYWECAHT_WEBHOOK_URL
SKY_IOS_DOWNLOAD_URL=$PRE_SKY_IOS_DOWNLOAD_URL SKY_IOS_DOWNLOAD_URL=$PRE_SKY_IOS_DOWNLOAD_URL
SKY_ANDROID_DOWNLOAD_URL=$PRE_SKY_ANDROID_DOWNLOAD_URL SKY_ANDROID_DOWNLOAD_URL=$PRE_SKY_ANDROID_DOWNLOAD_URL
XHJ_IOS_DOWNLOAD_URL=$PRE_XHJ_IOS_DOWNLOAD_URL XHJ_IOS_DOWNLOAD_URL=$PRE_XHJ_IOS_DOWNLOAD_URL
XHJ_ANDROID_DOWNLOAD_URL=$PRE_XHJ_ANDROID_DOWNLOAD_URL XHJ_ANDROID_DOWNLOAD_URL=$PRE_XHJ_ANDROID_DOWNLOAD_URL
elif [[ "${CI_COMMIT_BRANCH}" == "develop" ]] || [[ "${CI_COMMIT_BRANCH}" == "feat_devops" ]]; then elif [[ "${CI_COMMIT_BRANCH}" == "develop_sky" ]] || [[ "${CI_COMMIT_BRANCH}" == "feat_devops_sky" ]]; then
WECAHT_WEBHOOK_URL=$DEV_QYWECAHT_WEBHOOK_URL WECAHT_WEBHOOK_URL=$DEV_QYWECAHT_WEBHOOK_URL
SKY_IOS_DOWNLOAD_URL=$DEV_SKY_IOS_DOWNLOAD_URL SKY_IOS_DOWNLOAD_URL=$DEV_SKY_IOS_DOWNLOAD_URL
SKY_ANDROID_DOWNLOAD_URL=$DEV_SKY_ANDROID_DOWNLOAD_URL SKY_ANDROID_DOWNLOAD_URL=$DEV_SKY_ANDROID_DOWNLOAD_URL

View File

@ -127,7 +127,10 @@ dependencies:
sdk: flutter sdk: flutter
aliyun_face_plugin: aliyun_face_plugin:
path: aliyun_face_plugin path: aliyun_face_plugin
video_decode_plugin:
git:
url: git@code.star-lock.cn:liyi/video_decode_plugin.git
ref: 68bb4b7fb637ef5a78856908e1bc464f50fe967a
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
@ -277,6 +280,8 @@ dependencies:
video_thumbnail: ^0.5.3 video_thumbnail: ^0.5.3
# 角标管理 # 角标管理
flutter_app_badger: ^1.3.0 flutter_app_badger: ^1.3.0
# 滑块支持
slide_to_act: ^2.0.2

View File

@ -1,63 +1,106 @@
#!/bin/bash #!/bin/bash
# 读取环境变量 # ========================
URL=$CI_API_V4_URL # tag_generator.sh
TOKEN=$GITLAB_ACCESS_TOKEN # 用于自动识别并递增以 _sky 结尾的 tag如 v1.5.556_sky),并生成下一个 tag。
PROJECT_ID=$CI_PROJECT_ID # 递增规则feat: 提交递增 minorfix: 或其他提交递增 patch。
next_tag="" # ========================
newest_tag=""
# 读取环境变量(由 CI/CD 系统注入)
URL=$CI_API_V4_URL # GitLab API v4 地址
TOKEN=$GITLAB_ACCESS_TOKEN # GitLab 访问 Token
PROJECT_ID=$CI_PROJECT_ID # 当前项目 ID
next_tag="" # 新的 tag 变量
newest_tag="" # 最新 tag 变量
echo "PRIVATE-TOKEN: $TOKEN $URL/projects/$PROJECT_ID/repository/tags" echo "PRIVATE-TOKEN: $TOKEN $URL/projects/$PROJECT_ID/repository/tags"
# 获取项目所有 tag 的 json 列表
# 需要 jq 工具解析 json
# tags_json 是所有 tag 的原始 json 数据
tags_json=$(curl -H "Content-Type: application/json" -H "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/tags") tags_json=$(curl -H "Content-Type: application/json" -H "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/tags")
#echo "tags_json:$tags_json\n" # 提取所有 tag 名称,按时间倒序排列(最新的在前)
tags=$(echo "$tags_json" | jq -r '.[].name') tags=$(echo "$tags_json" | jq -r '.[].name')
tags_length=$(echo "$tags_json" | jq -r 'length')
if [ "$tags_length" -lt 1 ]; then # 只保留以 _sky 结尾的 tag即只处理 v1.2.3_sky 这种格式的 tag
next_tag="v1.0.0" sky_tags=$(echo "$tags" | grep '_sky$')
# 取最新的 _sky tag即第一个
newest_sky_tag=$(echo "$sky_tags" | head -n 1)
# 如果没有 _sky 结尾的 tag则从最新的 tag 提取版本号,生成 vX.Y.Z_sky
if [ -z "$newest_sky_tag" ]; then
# 取最新的 tag不管是否带_sky
latest_tag=$(echo "$tags" | head -n 1)
if [ -n "$latest_tag" ]; then
# 提取版本号部分去掉前缀v和后缀_sky等
version_part=${latest_tag#v} # 去掉v
version_part=${version_part%_sky} # 去掉_sky如果有
IFS='.' read -r major minor patch <<< "$version_part"
next_tag="v$major.$minor.$patch_sky"
else
next_tag="v1.0.0_sky"
fi
else else
newest_tag=$(echo "$tags" | head -n 1) # 解析版本号部分(去掉 _sky 后缀和 v 前缀)
IFS='.' read -r major minor patch <<< "$newest_tag" # 例如 v1.5.556_sky -> 1.5.556
major="${major#v}" version_part=${newest_sky_tag%_sky} # 去掉 _sky 后缀
compare_json="" version_part=${version_part#v} # 去掉 v 前缀
IFS='.' read -r major minor patch <<< "$version_part" # 拆分出主、次、修订号
compare_json="" # 用于存储 commit 对比结果
# 判断命令参数,决定对比范围
if [[ "$1" == "generate_tag" ]];then if [[ "$1" == "generate_tag" ]];then
echo "generate_tag:$newest_tag-to-master\n" # 生成 tag 时,对比最新 _sky tag 和 master 之间的提交
compare_json=$(curl -s --header "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/compare?from=$newest_tag&to=master") echo "generate_tag:$newest_sky_tag-to-master\n"
compare_json=$(curl -s --header "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/compare?from=$newest_sky_tag&to=master")
elif [[ "$1" == "generate_version" ]]; then elif [[ "$1" == "generate_version" ]]; then
echo "generate_version:master-to-$CI_COMMIT_BRANCH\n" # 生成版本号时,对比 master 和当前分支之间的提交
echo "generate_version:master-to-$CI_COMMIT_BRANCH\n"
compare_json=$(curl -s --header "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/compare?from=master&to=$CI_COMMIT_BRANCH") compare_json=$(curl -s --header "PRIVATE-TOKEN: $TOKEN" "$URL/projects/$PROJECT_ID/repository/compare?from=master&to=$CI_COMMIT_BRANCH")
fi fi
echo "compare_json:$compare_json\n" echo "compare_json:$compare_json\n"
new_patch=$patch new_patch=$patch # 新的 patch 号
new_minor=$minor new_minor=$minor # 新的 minor 号
# 遍历所有 commit根据提交信息递增版本号
while IFS= read -r commit_json; do while IFS= read -r commit_json; do
# 使用 jq 解析每一行的 JSON 对象 # 解析每个 commit 的 id 和 message
commit_id=$(echo "$commit_json" | jq -r '.id') commit_id=$(echo "$commit_json" | jq -r '.id')
commit_message=$(echo "$commit_json" | jq -r '.message') commit_message=$(echo "$commit_json" | jq -r '.message')
echo "----$commit_message" echo "----$commit_message"
# 如果有 feat: 类型提交minor 递增(只递增一次)
if [[ "$commit_message" =~ ("feat:"*) ]] && [[ $new_minor == $minor ]]; then if [[ "$commit_message" =~ ("feat:"*) ]] && [[ $new_minor == $minor ]]; then
((new_minor++)) ((new_minor++))
# new_patch=0 # 如果有 fix: 类型提交patch 递增
# break
elif [[ "$commit_message" =~ ("fix:"*) ]]; then elif [[ "$commit_message" =~ ("fix:"*) ]]; then
((new_patch++)) ((new_patch++))
# 其他类型提交(非 Merge/Revertpatch 递增
elif [[ ! "$commit_message" =~ ("Merge"* | "Revert"*) ]]; then elif [[ ! "$commit_message" =~ ("Merge"* | "Revert"*) ]]; then
((new_patch++)) ((new_patch++))
fi fi
done < <(echo "$compare_json" | jq -c '.commits[] | {id: .id, message: .message}') done < <(echo "$compare_json" | jq -c '.commits[] | {id: .id, message: .message}')
next_tag="v$major.$new_minor.$new_patch" # 组装新的 tag格式为 v<major>.<minor>.<patch>_sky
next_tag="v$major.$new_minor.$new_patch_sky"
fi fi
echo "New Tag:$newest_tag;New version: $next_tag;command: $1"
echo "New Tag:$newest_sky_tag;New version: $next_tag;command: $1"
# 如果是 generate_tag 命令,且新 tag 和最新 tag 一致,则跳过生成
if [[ "$1" == "generate_tag" ]];then if [[ "$1" == "generate_tag" ]];then
if [ "$next_tag" == "$newest_tag" ]; then if [ "$next_tag" == "$newest_sky_tag" ]; then
echo "no change from master,skip to generate tag" echo "no change from master,skip to generate tag"
exit 0 exit 0
fi fi
# 配置 git 用户名(可根据需要补充邮箱等)
git config user.name git config user.name
# 创建并推送新 tag
git tag $next_tag git tag $next_tag
git push -u origin $next_tag git push -u origin $next_tag
echo "generate tag: $next_tag" echo "generate tag: $next_tag"
elif [[ "$1" == "generate_version" ]]; then elif [[ "$1" == "generate_version" ]]; then
# 如果是 generate_version 命令,仅导出新版本号到环境变量
export NEXT_VERSION="$next_tag" export NEXT_VERSION="$next_tag"
echo "generate version: $NEXT_VERSION" echo "generate version: $NEXT_VERSION"
fi fi
# 无论哪种情况,都把新 tag 写入 app_new.version 文件
# 供后续流程使用
echo "$next_tag" > app_new.version echo "$next_tag" > app_new.version