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
- flutter
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release_sky"
- 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]+)?$/
.notify_rule:
@ -27,8 +27,8 @@ variables:
- macos
- flutter
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
.generate_tag_rule:
@ -36,16 +36,16 @@ variables:
- macos
- flutter
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_BRANCH == "master_sky"
.generate_next_version_rule:
tags:
- macos
- flutter
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "release"
- if: $CI_COMMIT_BRANCH == "canary_release"
- if: $CI_COMMIT_BRANCH == "develop_sky"
- if: $CI_COMMIT_BRANCH == "release_sky"
- if: $CI_COMMIT_BRANCH == "canary_release_sky"
- if: $CI_COMMIT_BRANCH =~ /feat_[a-zA-Z]+/
.print_env:

View File

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

View File

@ -8,8 +8,9 @@ export ENV_BUILD_WORKSPACE=${CI_PROJECT_DIR}
echo "GITLAB_WORKSPACE: ${CI_PROJECT_DIR}"
cd ${CI_PROJECT_DIR}/android
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]+)?$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release" ]]; then
# 只支持 v1.2.3_sky 这种tag格式
regex='^v[0-9]+\.[0-9]+\.[0-9]+_sky$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release_sky" ]]; then
echo "===build canary_release: ${NEXT_VERSION}"
export ENV_BUILD_TAG=${NEXT_VERSION}
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_bundle flavor:xhj_bundle --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}"
bundle exec fastlane beta flavor:xhj 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}"
bundle exec fastlane beta flavor:xhj env:pre --verbose
bundle exec fastlane beta flavor:sky env:pre --verbose

View File

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

View File

@ -9,8 +9,9 @@ echo "GITLAB_WORKSPACE: ${CI_PROJECT_DIR}"
cd ${CI_PROJECT_DIR}/ios
#bundle exec pod install
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]+)?$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release" ]]; then
# 只支持 v1.2.3_sky 这种tag格式
regex='^v[0-9]+\.[0-9]+\.[0-9]+_sky$'
if [[ "${ENV_BUILD_BRANCH}" == "canary_release_sky" ]]; then
echo "===build canary_release: ${NEXT_VERSION}"
export ENV_BUILD_TAG=${NEXT_VERSION}
bundle exec fastlane release_ipa flavor:xhj --verbose
@ -19,11 +20,11 @@ elif [[ $ENV_BUILD_TAG =~ $regex ]]; then
echo "===build release===$ENV_BUILD_TAG"
bundle exec fastlane release_ipa flavor:xhj --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}"
bundle exec fastlane beta flavor:xhj 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}"
bundle exec fastlane beta flavor:xhj 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/advancedFunctionRecord/advancedFunctionRecord_page.dart';
import 'package:star_lock/mine/valueAddedServices/valueAddedServicesRecord/value_added_services_record_page.dart';
import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_page.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_page.dart';
import 'package:star_lock/talk/starChart/webView/h264_web_view.dart';
@ -202,6 +203,7 @@ import 'mine/valueAddedServices/valueAddedServicesRealName/value_added_services_
import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesAddSMSTemplate/newSMSTemplate_page.dart';
import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesListSMSTemplate/customSMSTemplateList_page.dart';
import 'starLockApplication/starLockApplication.dart';
import 'talk/starChart/views/imageTransmission/image_transmission_page.dart';
import 'tools/seletKeyCyclicDate/seletKeyCyclicDate_page.dart';
abstract class Routers {
@ -514,6 +516,8 @@ abstract class Routers {
static const String starChartPage = '/starChartPage'; //
static const String starChartTalkView = '/starChartTalkView'; //
static const String h264WebView = '/h264WebView'; //
static const String imageTransmissionView =
'/imageTransmissionView'; //()
}
abstract class AppRouters {
@ -1184,6 +1188,13 @@ abstract class AppRouters {
page: () => const DoubleLockLinkPage()),
GetPage<dynamic>(
name: Routers.starChartTalkView, page: () => const TalkViewPage()),
GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()),
GetPage<dynamic>(
name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()),
//
GetPage<dynamic>(
name: Routers.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:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/flavors.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart';
@ -190,10 +191,12 @@ class BlueManage {
continue;
}
final isMatch = _isMatch(scanResult
.advertisementData.serviceUuids
.map((e) => e.uuid)
.toList());
final isMatch = _isMatch(
scanResult.advertisementData.serviceUuids
.map((e) => e.uuid)
.toList(),
isSingle: true,
);
if (isMatch && (scanResult.rssi >= -100)) {
// id相同的元素
@ -276,6 +279,7 @@ class BlueManage {
.map((e) => e.uuid)
.toList(),
deviceType: deviceType,
isSingle: false,
);
//
if (isMatch && (scanResult.rssi >= -100)) {
@ -320,26 +324,47 @@ class BlueManage {
/// uuid
bool _isMatch(List<String> serviceUuids,
{DeviceType deviceType = DeviceType.blue}) {
{DeviceType deviceType = DeviceType.blue, required bool isSingle}) {
final List<String> prefixes =
getDeviceType(deviceType).map((e) => e.toLowerCase()).toList();
for (String uuid in serviceUuids) {
final String cleanUuid = uuid.replaceAll('-', '').toLowerCase();
final String cleanUuid = uuid.toLowerCase();
if (cleanUuid.length == 8) {
// 8
// 845
String pairStatus = cleanUuid.substring(4, 6); // 4534
for (final prefix in prefixes) {
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
final String first8 = cleanUuid.substring(0, 8);
if (first8.length >= 4) {
final String thirdAndFourth = first8.substring(2, 4); // 23
if (cleanUuid.length >= 32) {
final String thirdAndFourth = cleanUuid.substring(2, 4); // 23
for (final prefix in prefixes) {
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 {
connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear();
BuglyTool.uploadException(
@ -598,7 +626,9 @@ class BlueManage {
});
} else {
connectStateCallBack(BluetoothConnectionState.disconnected);
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
if (!F.isSKY) {
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
}
scanDevices.clear();
BuglyTool.uploadException(
@ -791,7 +821,7 @@ class BlueManage {
}
}
//
///
Future<void> writeCharacteristicWithResponse(List<int> value) async {
final List<BluetoothService> services =
await bluetoothConnectDevice!.discoverServices();
@ -801,30 +831,70 @@ class BlueManage {
in service.characteristics) {
if (characteristic.characteristicUuid == _characteristicIdWrite) {
try {
//
int retryCount = 0;
const int maxRetries = 3;
const int retryDelayMs = 500;
final List<int> valueList = value;
final List subData = splitList(valueList, _mtuSize!);
// AppLog.log('writeCharacteristicWithResponse _mtuSize:$_mtuSize 得到的分割数据:$subData');
for (int i = 0; i < subData.length; i++) {
if (characteristic.properties.writeWithoutResponse) {
// 使WRITE_NO_RESPONSE属性写入值
await characteristic.write(subData[i], withoutResponse: true);
} else if (characteristic.properties.write) {
// 使WRITE属性写入值
await characteristic.write(subData[i]);
} else {
//
//
bool packetSent = false;
retryCount = 0;
while (!packetSent && retryCount < maxRetries) {
try {
if (characteristic.properties.writeWithoutResponse) {
await characteristic.write(subData[i],
withoutResponse: true);
} else if (characteristic.properties.write) {
await characteristic.write(subData[i]);
} else {
//
throw Exception(
'This characteristic does not support writing.');
}
//
packetSent = true;
} catch (e) {
if (e.toString().contains('UNKNOWN_GATT_ERROR (133)') &&
retryCount < maxRetries - 1) {
// GATT错误133
retryCount++;
AppLog.log(
'蓝牙写入失败(GATT 133),数据包 ${i + 1}/${subData.length} 正在重试 $retryCount/$maxRetries...');
await Future.delayed(
Duration(milliseconds: retryDelayMs));
continue;
} else {
//
AppLog.log('APP写入失败: $e');
throw e;
}
}
}
if (!packetSent) {
throw Exception(
'This characteristic does not support writing.');
'蓝牙写入失败,数据包 ${i + 1}/${subData.length} 已达到最大重试次数');
}
}
return; //
} on Exception catch (e, s) {
AppLog.log('APP写入失败: $e $s');
AppLog.log('APP写入失败: $e $s');
rethrow;
}
}
}
}
}
//
throw Exception('未找到适合写入的蓝牙特性');
}
//

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -88,7 +89,6 @@ class _LockDetailPageState extends State<LockDetailPage>
///
AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);
state.isOpenLockNeedOnline.refresh();
}
StreamSubscription? _lockRefreshLockDetailInfoDataEvent;
@ -1103,13 +1103,15 @@ class _LockDetailPageState extends State<LockDetailPage>
}));
//
showWidgetArr.add(bottomItem('images/main/icon_main_password.png', '密码'.tr,
state.bottomBtnisEable.value, () {
Get.toNamed(Routers.passwordKeyListPage,
arguments: <String, LockListInfoItemEntity>{
'keyInfo': state.keyInfos.value
});
}));
if (state.keyInfos.value.lockFeature!.password == 1) {
showWidgetArr.add(bottomItem('images/main/icon_main_password.png',
'密码'.tr, state.bottomBtnisEable.value, () {
Get.toNamed(Routers.passwordKeyListPage,
arguments: <String, LockListInfoItemEntity>{
'keyInfo': state.keyInfos.value
});
}));
}
// ic卡
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(
bottomItem('images/main/icon_catEyes.png', '监控'.tr,
state.bottomBtnisEable.value, () async {
@ -1467,7 +1469,7 @@ class _LockDetailPageState extends State<LockDetailPage>
state.iSOpenLock.value = true;
state.openLockBtnState.value = 1;
state.animationController!.forward();
// AppLog.log('点击开锁');
AppLog.log('点击开锁');
if (isOpenLockNeedOnline) {
//
state.openDoorModel = 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,13 +27,13 @@ class LockListLogic extends BaseGetXController {
LockListLogic(this.entity) {}
LockListState state = LockListState();
List<GroupList> _groupDataList = <GroupList>[];
final RxList<GroupList> groupDataList = <GroupList>[].obs;
LockListInfoGroupEntity? entity;
final ShowTipView showTipView = ShowTipView();
List<GroupList> get groupDataList {
List<GroupList> get groupDataListFiltered {
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) {
list.forEach((GroupList element) {
element.lockList?.removeWhere((LockListInfoItemEntity element) =>
@ -60,15 +60,12 @@ class LockListLogic extends BaseGetXController {
//
void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) {
this.entity = entity;
if (entity.pageNo == 1) {
_groupDataList = <GroupList>[];
}
_groupDataList.addAll(entity.groupList!);
update();
groupDataList.value = entity.groupList!;
}
//
late StreamSubscription<Reply> _replySubscription;
late StreamSubscription _setLockListInfoGroupEntity;
void _initReplySubscription() {
_replySubscription =
@ -336,17 +333,30 @@ class LockListLogic extends BaseGetXController {
void onReady() {
super.onReady();
_initReplySubscription();
_initEventHandler();
}
@override
void onInit() {
super.onInit();
// AppLog.log('onInit调用了 setLockListInfoGroupEntity');
setLockListInfoGroupEntity(entity!);
AppLog.log('[onInit] entity: \\${entity?.toString()}');
if (entity != null) {
setLockListInfoGroupEntity(entity!);
}
_initEventHandler();
}
@override
void onClose() {
_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
Widget build(BuildContext context) {
return GetBuilder<LockListLogic>(builder: (LockListLogic logic) {
return Scaffold(
body: ListView.separated(
itemCount: logic.groupDataList.length,
itemBuilder: (BuildContext context, int index) {
final GroupList itemData = logic.groupDataList[index];
return _buildLockExpandedList(context, index, itemData);
},
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 1,
color: AppColors.greyLineColor,
);
}),
);
});
return Obx(() => Scaffold(
body: ListView.separated(
itemCount: logic.groupDataListFiltered.length,
itemBuilder: (BuildContext context, int index) {
final GroupList itemData = logic.groupDataListFiltered[index];
return _buildLockExpandedList(context, index, itemData, key: ValueKey(itemData.groupId));
},
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 1,
color: AppColors.greyLineColor,
);
}),
));
}
//
Widget _buildLockExpandedList(BuildContext context, int index,
GroupList itemData) {
GroupList itemData, {Key? key}) {
final List<LockListInfoItemEntity> lockItemList =
itemData.lockList ?? <LockListInfoItemEntity>[];
return LockListGroupView(
key: key,
onTap: () {
//
if (itemData.isChecked) {} else {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:get/get.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
@ -13,6 +14,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/talk_accept.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';
@ -32,7 +35,7 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
//
startChartManage.stopTalkAcceptTimer();
//
_handleSendExpect();
_handleSendExpect(lockPeerID: scpMessage.FromPeerId!);
//
stopRingtone();
//
@ -76,8 +79,48 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
}
}
void _handleSendExpect() {
//
startChartManage.sendImageVideoAndG711AudioTalkExpectData();
///
void _handleSendExpect({
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/handle/other/h264_frame_handler.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
@ -61,9 +62,6 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
int? spTotal,
int? spIndex,
int? messageId}) {
//
final stats = PacketLossStatistics().getStatistics();
_asyncLog('丢包统计: $stats');
// _asyncLog(
// '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength');
if (messageType == MessageTypeConstant.RealTimeData) {
@ -118,7 +116,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
void _handleVideoH264(TalkData talkData) {
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
talkDataH264Frame.mergeFromBuffer(talkData.content);
frameHandler.handleFrame(talkDataH264Frame);
frameHandler.handleFrame(talkDataH264Frame, talkData);
}
///
@ -127,7 +125,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
await _processCompletePayload(Uint8List.fromList(talkData.content));
processCompletePayload.forEach((element) {
talkData.content = element;
talkDataRepository.addTalkData(talkData);
talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
});
}
@ -138,7 +140,11 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
// // pcm数据
// List<int> pcmBytes = G711().convertList(g711Data);
// talkData.content = pcmBytes;
talkDataRepository.addTalkData(talkData);
talkDataRepository.addTalkData(
TalkDataModel(
talkData: talkData,
),
);
} catch (e) {
print('Error decoding G.711 to PCM: $e');
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
@ -20,7 +21,7 @@ import '../../star_chart_manage.dart';
class UdpTalkExpectHandler extends ScpMessageBaseHandle
implements ScpMessageHandler {
final TalkViewState talkViewState = Get.put(TalkViewLogic()).state;
// final TalkViewState talkViewState = Get.put(TalkViewLogic()).state;
@override
void handleReq(ScpMessage scpMessage) {
@ -40,7 +41,11 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle
startChartManage.stopTalkExpectMessageTimer();
//
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秒内没有收到通话保持则执行的操作;
talkePingOverTimeTimerManager.start();

View File

@ -1,21 +1,19 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/gateway_reset.pb.dart';
import 'package:star_lock/talk/starChart/proto/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_request.pb.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/push/xs_jPhush.dart';
import 'package:star_lock/tools/storage.dart';
import 'package:star_lock/translations/current_locale_tool.dart';
@ -25,27 +23,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
RxString currentLanguage =
CurrentLocaleTool.getCurrentLocaleString().obs; //
//
int _lastRequestTime = 0;
@override
void handleReq(ScpMessage scpMessage) async {
final currentTime = DateTime.now().millisecondsSinceEpoch;
// 1
if (currentTime - _lastRequestTime < 1000) {
// 1
replyErrorMessage(scpMessage);
AppLog.log('对讲请求过于频繁,已拒绝');
return;
}
//
_lastRequestTime = currentTime;
//
final loginData = await Storage.getLoginData();
//
if (loginData != null &&
(talkStatus.status != TalkStatus.passiveCallWaitingAnswer ||
@ -56,7 +37,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
startChartManage.ToPeerId = scpMessage.FromPeerId!;
startChartManage.lockPeerId = scpMessage.FromPeerId!;
//
_talkRequestEvent(talkObjectName: talkReq.callerName);
_talkRequestEvent(
talkObjectName: talkReq.callerName,
lockPeerID: scpMessage.FromPeerId!,
);
//
replySuccessMessage(scpMessage);
@ -75,6 +59,11 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
//
startChartManage.FromPeerId = scpMessage.ToPeerId!;
startChartManage.ToPeerId = scpMessage.FromPeerId!;
startChartManage.lockPeerId = scpMessage.FromPeerId!;
//
_handleResponseSendExpect(
lockPeerID: scpMessage.FromPeerId!,
);
//
startChartManage.startTalkExpectTimer();
//
@ -95,28 +84,56 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
void handleRealTimeData(ScpMessage scpMessage) {}
//
void _talkRequestEvent({required String talkObjectName}) {
void _talkRequestEvent({
required String talkObjectName,
required String lockPeerID,
}) async {
//
_handleSendExpect();
_handleRequestSendExpect(lockPeerID: lockPeerID);
//
//test:使
playRingtone();
//
_showTalkRequestNotification(talkObjectName: talkObjectName);
// _showTalkRequestNotification(talkObjectName: talkObjectName);
//
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
.getDefaultTalkExpect()
.videoType
.indexOf(VideoTypeE.H264) ==
-1) {
.getDefaultTalkExpect()
.videoType
.contains(VideoTypeE.H264)) {
Get.toNamed(
Routers.starChartTalkView,
Routers.h264WebView,
);
} else {
Get.toNamed(
Routers.h264WebView,
Routers.starChartTalkView,
);
}
}
@ -188,8 +205,97 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
}
}
void _handleSendExpect() {
//
startChartManage.sendOnlyImageVideoTalkExpectData();
/// app收到的对讲请求后
void _handleRequestSendExpect({
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:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import '../../proto/talk_data_h264_frame.pb.dart';
class H264FrameHandler {
final void Function(List<int> frameData) onCompleteFrame;
final void Function(TalkDataModel frameData) onCompleteFrame;
H264FrameHandler({required this.onCompleteFrame});
void handleFrame(TalkDataH264Frame frame) {
onCompleteFrame(frame.frameData);
void handleFrame(TalkDataH264Frame frame, TalkData talkData) {
onCompleteFrame(
TalkDataModel(talkData: talkData, talkDataH264Frame: frame));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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:ui' as ui;
import 'dart:math'; // Import the math package to use sqrt
import 'dart:ui' show decodeImageFromList;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
@ -23,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/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/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';
@ -37,32 +41,24 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
final int minBufferSize = 2; // 2166ms
final int maxBufferSize = 20; // 8666ms
int bufferSize = 8; //
//
final int minAudioBufferSize = 1; // 1
final int maxAudioBufferSize = 3; // 3
int audioBufferSize = 2; // 2
int bufferSize = 8; //
//
int _startTime = 0; //
int _startAudioTime = 0; //
bool _isFirstFrame = true; //
int audioBufferSize = 2; // 2
bool _isFirstAudioFrame = true; //
int _startAudioTime = 0; //
//
final List<int> _bufferedAudioFrames = <int>[];
final Map<String, ui.Image> _imageCache = {};
//
bool _isListening = false;
StreamSubscription? _streamSubscription;
//
int _lastFrameTimestamp = 0; // 0
Timer? videoRenderTimer; //
//
int _frameCount = 0;
int _lastFpsUpdateTime = 0;
Timer? _fpsTimer;
int _renderedFrameCount = 0;
int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch;
///
void _initFlutterPcmSound() {
@ -96,18 +92,28 @@ class TalkViewLogic extends BaseGetXController {
//
void _startListenTalkData() {
state.talkDataRepository.talkDataStream.listen((TalkData talkData) async {
final contentType = talkData.contentType;
//
if (_isListening) {
AppLog.log("已经存在数据流监听,避免重复监听");
return;
}
AppLog.log("==== 启动新的数据流监听 ====");
_isListening = true;
_streamSubscription = state.talkDataRepository.talkDataStream
.listen((TalkDataModel talkDataModel) async {
final talkData = talkDataModel.talkData;
final contentType = talkData!.contentType;
final currentTime = DateTime.now().millisecondsSinceEpoch;
//
switch (contentType) {
case TalkData_ContentTypeE.G711:
// //
// if (_isFirstAudioFrame) {
// _startAudioTime = currentTime;
// _isFirstAudioFrame = false;
// }
if (_isFirstAudioFrame) {
_startAudioTime = currentTime;
_isFirstAudioFrame = false;
}
//
final expectedTime = _startAudioTime + talkData.durationMs;
@ -129,80 +135,16 @@ class TalkViewLogic extends BaseGetXController {
_playAudioFrames();
break;
case TalkData_ContentTypeE.Image:
//
if (_isFirstFrame) {
_startTime = currentTime;
_isFirstFrame = false;
AppLog.log('第一帧帧的时间戳:${talkData.durationMs}');
}
// AppLog.log('其他帧的时间戳:${talkData.durationMs}');
//
if (_lastFrameTimestamp != 0) {
final int frameInterval = talkData.durationMs - _lastFrameTimestamp;
_adjustBufferSize(frameInterval); //
}
_lastFrameTimestamp = talkData.durationMs; //
//
if (state.videoBuffer.length >= bufferSize) {
state.videoBuffer.removeAt(0);
}
// bufferSize帧
state.videoBuffer.add(talkData);
//
await _decodeAndCacheFrame(talkData);
//
_playVideoFrames();
if (state.videoBuffer.length > bufferSize) {
state.videoBuffer.removeAt(0); //
}
break;
}
});
}
//
void _playVideoFrames() {
//
if (state.videoBuffer.isEmpty || state.videoBuffer.length < bufferSize) {
// AppLog.log('📊 缓冲中 - 当前缓冲区大小: ${state.videoBuffer.length}/${bufferSize}');
return;
}
//
TalkData? oldestFrame;
int oldestIndex = -1;
for (int i = 0; i < state.videoBuffer.length; i++) {
if (oldestFrame == null ||
state.videoBuffer[i].durationMs < oldestFrame.durationMs) {
oldestFrame = state.videoBuffer[i];
oldestIndex = i;
}
}
//
if (oldestFrame != null && oldestIndex != -1) {
final cacheKey = oldestFrame.content.hashCode.toString();
// 使
if (_imageCache.containsKey(cacheKey)) {
state.currentImage.value = _imageCache[cacheKey];
state.listData.value = Uint8List.fromList(oldestFrame.content);
state.videoBuffer.removeAt(oldestIndex); //
// //
// _frameCount++;
// final currentTime = DateTime.now().millisecondsSinceEpoch;
// final elapsed = currentTime - _lastFpsUpdateTime;
//
// if (elapsed >= 1000) {
// //
// state.fps.value = (_frameCount * 1000 / elapsed).round();
// _frameCount = 0;
// _lastFpsUpdateTime = currentTime;
// }
} else {
// AppLog.log('⚠️ 帧未找到缓存 - Key: $cacheKey');
state.videoBuffer.removeAt(oldestIndex); //
}
}
}
//
void _playAudioFrames() {
//
@ -233,50 +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() {
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 {
final Permission permission = Permission.microphone;
@ -498,6 +382,31 @@ class TalkViewLogic extends BaseGetXController {
requestPermissions();
// 10fps
videoRenderTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
final int now = DateTime.now().millisecondsSinceEpoch;
if (state.videoBuffer.isNotEmpty) {
final TalkData oldestFrame = state.videoBuffer.removeAt(0);
if (oldestFrame.content.isNotEmpty) {
state.listData.value = Uint8List.fromList(oldestFrame.content); //
final int decodeStart = DateTime.now().millisecondsSinceEpoch;
decodeImageFromList(Uint8List.fromList(oldestFrame.content)).then((ui.Image img) {
final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
state.currentImage.value = img;
_renderedFrameCount++;
// fps
if (now - _lastFpsPrintTime >= 1000) {
// print('实际渲染fps: $_renderedFrameCount');
_renderedFrameCount = 0;
_lastFpsPrintTime = now;
}
}).catchError((e) {
print('图片解码失败: $e');
});
}
}
//
});
}
@override
@ -512,10 +421,17 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio();
//
_imageCache.clear();
// _imageCache.clear();
state.oneMinuteTimeTimer?.cancel(); //
state.oneMinuteTimeTimer = null; //
state.oneMinuteTime.value = 0;
//
_streamSubscription?.cancel();
_isListening = false;
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.onClose();
}
@ -525,6 +441,9 @@ class TalkViewLogic extends BaseGetXController {
stopProcessingAudio();
//
StartChartManage().reSetDefaultTalkExpect();
//
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.dispose();
}
@ -597,34 +516,40 @@ class TalkViewLogic extends BaseGetXController {
//
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 lockListPeerId = StartChartManage().lockListPeerId;
int lockId = lockDetailState.keyInfos.value.lockId ?? 0;
// peerId使peerId
// peerId
lockListPeerId.forEach((element) {
if (element.network?.peerId == lockPeerId) {
lockId = element.lockId ?? 0;
}
});
final LockSetInfoEntity lockSetInfoEntity =
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 = [];
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;
}
}
}
}
} 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/handle/impl/debug_Info_model.dart';
import 'package:star_lock/talk/starChart/handle/impl/udp_talk_data_handler.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_logic.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
@ -30,6 +31,7 @@ class _TalkViewPageState extends State<TalkViewPage>
final TalkViewLogic logic = Get.put(TalkViewLogic());
final TalkViewState state = Get.find<TalkViewLogic>().state;
late Stream<int> _latencyStream;
final startChartManage = StartChartManage();
@override
void initState() {
@ -96,60 +98,55 @@ class _TalkViewPageState extends State<TalkViewPage>
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Obx(
() {
final double screenWidth = MediaQuery.of(context).size.width;
final double screenHeight = MediaQuery.of(context).size.height;
final double logicalWidth = MediaQuery.of(context).size.width;
final double logicalHeight = MediaQuery.of(context).size.height;
final double devicePixelRatio =
MediaQuery.of(context).devicePixelRatio;
//
final double physicalWidth = logicalWidth * devicePixelRatio;
final double physicalHeight = logicalHeight * devicePixelRatio;
//
const int rotatedImageWidth = 480; //
const int rotatedImageHeight = 864; //
//
final double scaleWidth = physicalWidth / rotatedImageWidth;
final double scaleHeight = physicalHeight / rotatedImageHeight;
max(scaleWidth, scaleHeight); //
return state.listData.value.isEmpty
? Image.asset(
'images/main/monitorBg.png',
width: screenWidth,
height: screenHeight,
fit: BoxFit.cover,
)
: PopScope(
canPop: false,
child: RepaintBoundary(
key: state.globalKey,
child: SizedBox.expand(
child: RotatedBox(
quarterTurns: -1,
child: Obx(
() => state.currentImage.value != null
? RawImage(
image: state.currentImage.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
)
: Container(color: Colors.transparent),
),
),
),
),
);
},
),
//
Obx(() {
if (state.listData.value.isEmpty) {
return SizedBox.expand(
child: Image.asset(
'images/main/monitorBg.png',
fit: BoxFit.cover,
),
);
}
final int videoW = startChartManage.videoWidth;
final int videoH = startChartManage.videoHeight;
if (videoW == 320 && videoH == 240) {
return SizedBox.expand(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF232526),
Color(0xFF414345),
],
),
),
),
);
}
return const SizedBox.shrink();
}),
//
Obx(() {
if (state.listData.value.isEmpty) {
return const SizedBox.shrink();
}
final int videoW = startChartManage.videoWidth;
final int videoH = startChartManage.videoHeight;
if (videoW == 320 && videoH == 240) {
return Positioned(
top: 150.h,
left: 0,
right: 0,
child: _buildVideoWidget(),
);
} else {
//
return _buildVideoWidget();
}
}),
Obx(() => state.listData.value.isEmpty
? Positioned(
bottom: 310.h,
@ -158,35 +155,35 @@ class _TalkViewPageState extends State<TalkViewPage>
style: TextStyle(color: Colors.black, fontSize: 26.sp),
))
: Container()),
Obx(
() => state.listData.value.isNotEmpty &&
state.oneMinuteTime.value > 0
? Positioned(
top: ScreenUtil().statusBarHeight + 75.h,
width: 1.sw,
child: Obx(
() {
final String sec = (state.oneMinuteTime.value % 60)
.toString()
.padLeft(2, '0');
final String min = (state.oneMinuteTime.value ~/ 60)
.toString()
.padLeft(2, '0');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$min:$sec',
style: TextStyle(
fontSize: 26.sp, color: Colors.white),
),
],
);
},
),
)
: Container(),
),
Obx(() =>
state.listData.value.isNotEmpty && state.oneMinuteTime.value > 0
? Positioned(
top: ScreenUtil().statusBarHeight + 75.h,
width: 1.sw,
child: Obx(
() {
final String sec = (state.oneMinuteTime.value % 60)
.toString()
.padLeft(2, '0');
final String min = (state.oneMinuteTime.value ~/ 60)
.toString()
.padLeft(2, '0');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$min:$sec',
style: TextStyle(
fontSize: 26.sp, color: Colors.white),
),
],
);
},
),
)
: Container()),
///
Positioned(
bottom: 10.w,
child: Container(
@ -456,7 +453,6 @@ class _TalkViewPageState extends State<TalkViewPage>
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully &&
// state.listData.value.length > 0) {
// logic.udpOpenDoorAction();
logic.remoteOpenLock();
// }
// if (UDPManage().remoteUnlock == 1) {
// logic.udpOpenDoorAction();
@ -464,6 +460,7 @@ class _TalkViewPageState extends State<TalkViewPage>
// } else {
// logic.showToast('请在锁设置中开启远程开锁'.tr);
// }
logic.remoteOpenLock();
},
)
]);
@ -618,4 +615,68 @@ class _TalkViewPageState extends State<TalkViewPage>
// UdpTalkDataHandler().resetDataRates();
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 hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
//
final Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
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';
@ -195,6 +196,7 @@ class RogerThatLockInfoDataEvent {
class GetGatewayListRefreshUI {
GetGatewayListRefreshUI();
}
///
class AgreePrivacyAgreement {
AgreePrivacyAgreement();
@ -204,3 +206,10 @@ class AgreePrivacyAgreement {
class SuccessfulDistributionNetwork {
SuccessfulDistributionNetwork();
}
///
class SetLockListInfoGroupEntity {
SetLockListInfoGroupEntity({required this.lockListInfoGroupEntity});
LockListInfoGroupEntity lockListInfoGroupEntity;
}

View File

@ -2,13 +2,13 @@
set -e
APP_PRODUCT_NAME=$APP_PRODUCT_NAME
BUILD_STATUS=$1
if [[ "${CI_COMMIT_BRANCH}" == "release" ]] ; then
if [[ "${CI_COMMIT_BRANCH}" == "release_sky" ]] ; then
WECAHT_WEBHOOK_URL=$PRE_QYWECAHT_WEBHOOK_URL
SKY_IOS_DOWNLOAD_URL=$PRE_SKY_IOS_DOWNLOAD_URL
SKY_ANDROID_DOWNLOAD_URL=$PRE_SKY_ANDROID_DOWNLOAD_URL
XHJ_IOS_DOWNLOAD_URL=$PRE_XHJ_IOS_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
SKY_IOS_DOWNLOAD_URL=$DEV_SKY_IOS_DOWNLOAD_URL
SKY_ANDROID_DOWNLOAD_URL=$DEV_SKY_ANDROID_DOWNLOAD_URL

View File

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

View File

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