Merge branch 'develop_sky' into 'release_sky'
Develop sky See merge request StarlockTeam/app-starlock!271
This commit is contained in:
commit
c9683b4cba
2330
lan/lan_ar.json
2330
lan/lan_ar.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_bg.json
2332
lan/lan_bg.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_bn.json
2332
lan/lan_bn.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_cs.json
2332
lan/lan_cs.json
File diff suppressed because one or more lines are too long
2332
lan/lan_da.json
2332
lan/lan_da.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_de.json
2332
lan/lan_de.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_el.json
2332
lan/lan_el.json
File diff suppressed because it is too large
Load Diff
2348
lan/lan_en.json
2348
lan/lan_en.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_es.json
2332
lan/lan_es.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_et.json
2332
lan/lan_et.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_fi.json
2332
lan/lan_fi.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_fr.json
2332
lan/lan_fr.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_he.json
2332
lan/lan_he.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_hi.json
2332
lan/lan_hi.json
File diff suppressed because it is too large
Load Diff
@ -1162,5 +1162,11 @@
|
|||||||
"锁语音包设置": "鎖語音包設定",
|
"锁语音包设置": "鎖語音包設定",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "男聲",
|
"男声": "男聲",
|
||||||
"女声": "女聲"
|
"女声": "女聲",
|
||||||
|
"您的图像和视频数据仅保留": "您的圖像和視頻數據僅保留",
|
||||||
|
"后图像和视频数据将会失效,开通": "后圖像和視頻數據將會失效,開通",
|
||||||
|
"云存会员": "雲存會員",
|
||||||
|
"服务,图像视频信息随心存!": "服務,圖像視頻資訊隨心存!",
|
||||||
|
"图像": "圖像",
|
||||||
|
"视频": "視頻"
|
||||||
}
|
}
|
||||||
2332
lan/lan_hr.json
2332
lan/lan_hr.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_hu.json
2332
lan/lan_hu.json
File diff suppressed because it is too large
Load Diff
2344
lan/lan_hy.json
2344
lan/lan_hy.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_id.json
2332
lan/lan_id.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_it.json
2332
lan/lan_it.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_ja.json
2332
lan/lan_ja.json
File diff suppressed because it is too large
Load Diff
2344
lan/lan_ka.json
2344
lan/lan_ka.json
File diff suppressed because it is too large
Load Diff
@ -1172,5 +1172,11 @@
|
|||||||
"语音包设置": "语音包设置",
|
"语音包设置": "语音包设置",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "男声",
|
"男声": "男声",
|
||||||
"女声": "女声"
|
"女声": "女声",
|
||||||
|
"您的图像和视频数据仅保留": "您的图像和视频数据仅保留",
|
||||||
|
"后图像和视频数据将会失效,开通": "后图像和视频数据将会失效,开通",
|
||||||
|
"云存会员": "云存会员",
|
||||||
|
"服务,图像视频信息随心存!": "服务,图像视频信息随心存!",
|
||||||
|
"图像": "图像",
|
||||||
|
"视频": "视频"
|
||||||
}
|
}
|
||||||
|
|||||||
2332
lan/lan_kk.json
2332
lan/lan_kk.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_ko.json
2332
lan/lan_ko.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_lt.json
2332
lan/lan_lt.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_ms.json
2332
lan/lan_ms.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_nl.json
2332
lan/lan_nl.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_pl.json
2332
lan/lan_pl.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_pt.json
2332
lan/lan_pt.json
File diff suppressed because it is too large
Load Diff
@ -1166,5 +1166,11 @@
|
|||||||
"语音包设置": "Configurações do pacote de voz",
|
"语音包设置": "Configurações do pacote de voz",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "Macho",
|
"男声": "Macho",
|
||||||
"女声": "Garota"
|
"女声": "Garota",
|
||||||
|
"您的图像和视频数据仅保留": "Seus dados de imagem e vídeo são retidos apenas",
|
||||||
|
"后图像和视频数据将会失效,开通": "Depois disso, os dados de imagem e vídeo serão inválidos e ativados",
|
||||||
|
"云存会员": "Associação de armazenamento em nuvem",
|
||||||
|
"服务,图像视频信息随心存!": "Informações de serviço, imagem e vídeo estão no seu coração!",
|
||||||
|
"图像": "imagem",
|
||||||
|
"视频": "Vídeo"
|
||||||
}
|
}
|
||||||
2332
lan/lan_ro.json
2332
lan/lan_ro.json
File diff suppressed because it is too large
Load Diff
2340
lan/lan_ru.json
2340
lan/lan_ru.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_sk.json
2332
lan/lan_sk.json
File diff suppressed because it is too large
Load Diff
@ -1161,5 +1161,11 @@
|
|||||||
"锁语音包设置": "Закључајте подешавања говорног пакета",
|
"锁语音包设置": "Закључајте подешавања говорног пакета",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "мушки глас",
|
"男声": "мушки глас",
|
||||||
"女声": "женски глас"
|
"女声": "женски глас",
|
||||||
|
"您的图像和视频数据仅保留": "Ваши подаци о слици и видео записима се задржавају само",
|
||||||
|
"后图像和视频数据将会失效,开通": "Након тога, сликовни и видео подаци ће бити неважећи и активирани",
|
||||||
|
"云存会员": "Чланство у облаку за складиштење",
|
||||||
|
"服务,图像视频信息随心存!": "Сервис , слике и видео информације су у вашем срцу!",
|
||||||
|
"图像": "Слика",
|
||||||
|
"视频": "Пријава"
|
||||||
}
|
}
|
||||||
2332
lan/lan_sv.json
2332
lan/lan_sv.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_th.json
2332
lan/lan_th.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_tr.json
2332
lan/lan_tr.json
File diff suppressed because it is too large
Load Diff
@ -1161,5 +1161,11 @@
|
|||||||
"锁语音包设置": "鎖語音包設定",
|
"锁语音包设置": "鎖語音包設定",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "男聲",
|
"男声": "男聲",
|
||||||
"女声": "女聲"
|
"女声": "女聲",
|
||||||
|
"您的图像和视频数据仅保留": "您的圖像和視頻數據僅保留",
|
||||||
|
"后图像和视频数据将会失效,开通": "后圖像和視頻數據將會失效,開通",
|
||||||
|
"云存会员": "雲存會員",
|
||||||
|
"服务,图像视频信息随心存!": "服務,圖像視頻資訊隨心存!",
|
||||||
|
"图像": "圖像",
|
||||||
|
"视频": "視頻"
|
||||||
}
|
}
|
||||||
2332
lan/lan_uk.json
2332
lan/lan_uk.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_ur.json
2332
lan/lan_ur.json
File diff suppressed because it is too large
Load Diff
2332
lan/lan_vi.json
2332
lan/lan_vi.json
File diff suppressed because it is too large
Load Diff
@ -63,7 +63,6 @@
|
|||||||
"授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人": "授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人",
|
"授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人": "授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人",
|
||||||
"功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。": "功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。",
|
"功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。": "功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。",
|
||||||
"此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行",
|
"此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行",
|
||||||
|
|
||||||
"功能开启后,你将可以通过网关远程开锁。": "功能开启后,你将可以通过网关远程开锁。",
|
"功能开启后,你将可以通过网关远程开锁。": "功能开启后,你将可以通过网关远程开锁。",
|
||||||
"排列方式": "排列方式",
|
"排列方式": "排列方式",
|
||||||
"早到榜": "早到榜",
|
"早到榜": "早到榜",
|
||||||
@ -1102,7 +1101,7 @@
|
|||||||
"支持的国家": "支持的国家",
|
"支持的国家": "支持的国家",
|
||||||
"支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本",
|
"支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本",
|
||||||
"操作流程": "操作流程",
|
"操作流程": "操作流程",
|
||||||
"操作流程值":"1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了",
|
"操作流程值": "1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了",
|
||||||
"Google Home": "Google Home",
|
"Google Home": "Google Home",
|
||||||
"Action name": "Action name",
|
"Action name": "Action name",
|
||||||
"ScienerSmart": "ScienerSmart",
|
"ScienerSmart": "ScienerSmart",
|
||||||
@ -1174,5 +1173,11 @@
|
|||||||
"语音包设置": "语音包设置",
|
"语音包设置": "语音包设置",
|
||||||
"(中国台湾)": "(中国台湾)",
|
"(中国台湾)": "(中国台湾)",
|
||||||
"男声": "男声",
|
"男声": "男声",
|
||||||
"女声": "女声"
|
"女声": "女声",
|
||||||
}
|
"您的图像和视频数据仅保留": "您的图像和视频数据仅保留",
|
||||||
|
"后图像和视频数据将会失效,开通": "后图像和视频数据将会失效,开通",
|
||||||
|
"云存会员": "云存会员",
|
||||||
|
"服务,图像视频信息随心存!": "服务,图像视频信息随心存!",
|
||||||
|
"图像": "图像",
|
||||||
|
"视频": "视频"
|
||||||
|
}
|
||||||
@ -48,7 +48,7 @@ class ReadLockCurrentVoicePacketReply extends Reply {
|
|||||||
CommandType commandType, List<int> dataDetail)
|
CommandType commandType, List<int> dataDetail)
|
||||||
: super.parseData(commandType, dataDetail) {
|
: super.parseData(commandType, dataDetail) {
|
||||||
data = dataDetail;
|
data = dataDetail;
|
||||||
status = data[6];
|
status = data[2];
|
||||||
errorWithStstus(status);
|
errorWithStstus(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ class SetVoicePackageFinalResultReply extends Reply {
|
|||||||
CommandType commandType, List<int> dataDetail)
|
CommandType commandType, List<int> dataDetail)
|
||||||
: super.parseData(commandType, dataDetail) {
|
: super.parseData(commandType, dataDetail) {
|
||||||
data = dataDetail;
|
data = dataDetail;
|
||||||
status = data[6];
|
status = data[2];
|
||||||
errorWithStstus(status);
|
errorWithStstus(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,11 @@ import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart';
|
|||||||
import 'package:star_lock/blue/io_protocol/io_readAdminPassword.dart';
|
import 'package:star_lock/blue/io_protocol/io_readAdminPassword.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsNoParameters.dart';
|
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsNoParameters.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsWithParameters.dart';
|
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsWithParameters.dart';
|
||||||
|
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart';
|
import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsNoParameters.dart';
|
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsNoParameters.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
|
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
|
||||||
|
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_timing.dart';
|
import 'package:star_lock/blue/io_protocol/io_timing.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
||||||
@ -317,6 +319,18 @@ class CommandReciverManager {
|
|||||||
commandType, data);
|
commandType, data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case CommandType.readLockCurrentVoicePacket:
|
||||||
|
{
|
||||||
|
reply =
|
||||||
|
ReadLockCurrentVoicePacketReply.parseData(commandType, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CommandType.setLockCurrentVoicePacket:
|
||||||
|
{
|
||||||
|
reply =
|
||||||
|
SetVoicePackageFinalResultReply.parseData(commandType, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case CommandType.generalExtendedCommond:
|
case CommandType.generalExtendedCommond:
|
||||||
{
|
{
|
||||||
// 子命令类型
|
// 子命令类型
|
||||||
|
|||||||
@ -125,4 +125,9 @@ class DoorLockLogDataItem {
|
|||||||
data['recordDetailStr'] = recordDetailStr;
|
data['recordDetailStr'] = recordDetailStr;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DoorLockLogDataItem{recordId: $recordId, lockId: $lockId, lockAlias: $lockAlias, recordType: $recordType, recordTypeName: $recordTypeName, username: $username, operateDate: $operateDate, imagesUrl: $imagesUrl, videoUrl: $videoUrl, headUrl: $headUrl, userid: $userid, keyboardPwd: $keyboardPwd, recordStr: $recordStr, recordDetailStr: $recordDetailStr}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,15 @@ import 'dart:async';
|
|||||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:star_lock/apm/apm_helper.dart';
|
import 'package:star_lock/apm/apm_helper.dart';
|
||||||
|
import 'package:star_lock/appRouters.dart';
|
||||||
import 'package:star_lock/app_settings/app_settings.dart';
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
|
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/date_time_extensions.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/date_time_extensions.dart';
|
||||||
|
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
|
||||||
import 'package:star_lock/main/lockDetail/lockOperatingRecord/lockOperatingRecordGetLastRecordTime_entity.dart';
|
import 'package:star_lock/main/lockDetail/lockOperatingRecord/lockOperatingRecordGetLastRecordTime_entity.dart';
|
||||||
|
import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart';
|
||||||
import 'package:star_lock/tools/commonDataManage.dart';
|
import 'package:star_lock/tools/commonDataManage.dart';
|
||||||
import 'package:star_lock/tools/dateTool.dart';
|
import 'package:star_lock/tools/dateTool.dart';
|
||||||
import 'package:star_lock/tools/eventBusEventManage.dart';
|
import 'package:star_lock/tools/eventBusEventManage.dart';
|
||||||
@ -402,7 +405,20 @@ class DoorLockLogLogic extends BaseGetXController {
|
|||||||
void refreshWeek() {
|
void refreshWeek() {
|
||||||
_setWeekRange();
|
_setWeekRange();
|
||||||
}
|
}
|
||||||
|
getWebPlayUrl() async {
|
||||||
|
final AdvancedFeaturesWebEntity entity =
|
||||||
|
await ApiRepository.to.getServicePackageBuyUrl();
|
||||||
|
if (entity.errorCode!.codeIsSuccessful) {
|
||||||
|
state.cloudStorageWebViewUrl.value = entity.data!.cloudStorage!;
|
||||||
|
final uploadReportBuyRequest = await ApiRepository.to
|
||||||
|
.uploadReportBuyRequest(lockId: state.keyInfos.value.lockId!);
|
||||||
|
if (uploadReportBuyRequest.errorCode!.codeIsSuccessful) {
|
||||||
|
Get.toNamed(Routers.advancedFeaturesWebPage, arguments: <String, int>{
|
||||||
|
'webBuyType': XSConstantMacro.webBuyTypeCloudStorage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
Future<void> onClose() async {
|
Future<void> onClose() async {
|
||||||
super.onClose();
|
super.onClose();
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flustars/flustars.dart';
|
import 'package:flustars/flustars.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
@ -284,6 +285,17 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
return formatter.format(dateTime);
|
return formatter.format(dateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _checkIsVideoOrImagesType(DoorLockLogDataItem item) {
|
||||||
|
final recordType = item.recordType;
|
||||||
|
switch (recordType) {
|
||||||
|
case 130:
|
||||||
|
case 220:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _buildIDByType(DoorLockLogDataItem item) {
|
String _buildIDByType(DoorLockLogDataItem item) {
|
||||||
final recordType = item.recordType;
|
final recordType = item.recordType;
|
||||||
switch (recordType) {
|
switch (recordType) {
|
||||||
@ -407,6 +419,20 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
itemCount: state.lockLogItemList.length,
|
itemCount: state.lockLogItemList.length,
|
||||||
contentsBuilder: (BuildContext context, int index) {
|
contentsBuilder: (BuildContext context, int index) {
|
||||||
final DoorLockLogDataItem timelineData = state.lockLogItemList[index];
|
final DoorLockLogDataItem timelineData = state.lockLogItemList[index];
|
||||||
|
// 👇 提前计算第一个有 videoUrl 的索引(可以在 build 外层计算一次,避免重复)
|
||||||
|
int? firstVideoIndex = state.lockLogItemList
|
||||||
|
.indexWhere((item) => _checkIsVideoOrImagesType(item));
|
||||||
|
bool isInvalid = _checkIsVideoOrImagesType(timelineData) &&
|
||||||
|
((timelineData.imagesUrl == null &&
|
||||||
|
timelineData.videoUrl == null) ||
|
||||||
|
(timelineData.videoUrl == '' && timelineData.imagesUrl == ''));
|
||||||
|
|
||||||
|
String typeText = '';
|
||||||
|
if (timelineData.recordType == 130) {
|
||||||
|
typeText = '图像'.tr;
|
||||||
|
} else if (timelineData.recordType == 220) {
|
||||||
|
typeText = '视频'.tr;
|
||||||
|
}
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
@ -428,17 +454,37 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
// 使用 SingleChildScrollView 实现横向滚动
|
// 使用 SingleChildScrollView 实现横向滚动
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal, // 横向滚动
|
scrollDirection: Axis.horizontal, // 横向滚动
|
||||||
child: Text(
|
child: RichText(
|
||||||
_buildIDByType(timelineData),
|
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
text: TextSpan(
|
||||||
color: _buildTextColorByType(timelineData),
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
color: _buildTextColorByType(timelineData),
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 24.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: _buildIDByType(timelineData) +
|
||||||
|
(isInvalid
|
||||||
|
? '(${typeText}' +
|
||||||
|
'已失效'.tr +
|
||||||
|
')'
|
||||||
|
: ''),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Visibility(
|
||||||
|
visible: isInvalid,
|
||||||
|
child: Icon(
|
||||||
|
Icons.error,
|
||||||
|
size: 24.sp,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// 关键:禁用换行,强制单行显示
|
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
// 可选:添加省略号(如果文本过长)
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -455,8 +501,71 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 20.h,
|
height: 12.h,
|
||||||
),
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: _checkIsVideoOrImagesType(timelineData) &&
|
||||||
|
index == firstVideoIndex,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await logic.getWebPlayUrl();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
// 普通文字部分
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
'${'您的图像和视频数据仅保留'.tr} ${state.rollingStorageDays.value} ${'天'.tr} ,${state.rollingStorageDays.value} ${'天'.tr} ${'后图像和视频数据将会失效,开通'.tr}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
height: 1.8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 🔥 高亮且可点击的“云存会员”文本
|
||||||
|
TextSpan(
|
||||||
|
text: '云存会员'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.mainColor,
|
||||||
|
fontSize: 22.sp,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
// 更粗
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationThickness: 1.5,
|
||||||
|
height: 1.8,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () async {
|
||||||
|
// 👉 点击回调:跳转或弹窗
|
||||||
|
print('点击了“云存会员”');
|
||||||
|
await logic.getWebPlayUrl();
|
||||||
|
// 例如:Navigator.push(context, MaterialPageRoute(builder: ...));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// 后续文字
|
||||||
|
TextSpan(
|
||||||
|
text: '服务,图像视频信息随心存!'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
height: 1.8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_rx/get_rx.dart';
|
||||||
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
|
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
|
||||||
import 'package:star_lock/tools/advancedCalendar/src/controller.dart';
|
import 'package:star_lock/tools/advancedCalendar/src/controller.dart';
|
||||||
@ -73,4 +74,6 @@ class DoorLockLogState {
|
|||||||
int logCountPage = 10; // 蓝牙记录一页多少个
|
int logCountPage = 10; // 蓝牙记录一页多少个
|
||||||
Rx<DateTime> currentSelectDate = DateTime.now().obs;
|
Rx<DateTime> currentSelectDate = DateTime.now().obs;
|
||||||
bool isLockReceiveResponse = false; // 是否收到回复
|
bool isLockReceiveResponse = false; // 是否收到回复
|
||||||
|
RxString cloudStorageWebViewUrl = ''.obs;
|
||||||
|
RxInt rollingStorageDays = 3.obs; //滚动存储日期
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,27 +89,27 @@ class _CatEyeCustomModePageState extends State<CatEyeCustomModePage> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 30.h,
|
height: 30.h,
|
||||||
),
|
),
|
||||||
Container(
|
// Container(
|
||||||
margin: EdgeInsets.only(left: 20.w),
|
// margin: EdgeInsets.only(left: 20.w),
|
||||||
child: CommonItem(
|
// child: CommonItem(
|
||||||
leftTitel: '实时画面'.tr,
|
// leftTitel: '实时画面'.tr,
|
||||||
rightTitle: state.realTimeMode.value,
|
// rightTitle: state.realTimeMode.value,
|
||||||
isHaveLine: false,
|
// isHaveLine: false,
|
||||||
isHaveDirection: true,
|
// isHaveDirection: true,
|
||||||
isHaveRightWidget: false,
|
// isHaveRightWidget: false,
|
||||||
action: () {
|
// action: () {
|
||||||
Navigator.pushNamed(context, Routers.liveVideoPage,
|
// Navigator.pushNamed(context, Routers.liveVideoPage,
|
||||||
arguments: {
|
// arguments: {
|
||||||
'lockSetInfoData': state.lockSetInfoData.value,
|
// 'lockSetInfoData': state.lockSetInfoData.value,
|
||||||
'catEyeConfigData': state.lockSetInfoData.value
|
// 'catEyeConfigData': state.lockSetInfoData.value
|
||||||
.lockSettingInfo!.catEyeConfig!.isNotEmpty
|
// .lockSettingInfo!.catEyeConfig!.isNotEmpty
|
||||||
? state.lockSetInfoData.value.lockSettingInfo!
|
// ? state.lockSetInfoData.value.lockSettingInfo!
|
||||||
.catEyeConfig![0]
|
// .catEyeConfig![0]
|
||||||
: null
|
// : null
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/blue/blue_manage.dart';
|
import 'package:star_lock/blue/blue_manage.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
|
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
|
||||||
import 'package:star_lock/blue/io_reply.dart';
|
import 'package:star_lock/blue/io_reply.dart';
|
||||||
@ -82,30 +83,31 @@ class CatEyeSetLogic extends BaseGetXController {
|
|||||||
//成功
|
//成功
|
||||||
cancelBlueConnetctToastTimer();
|
cancelBlueConnetctToastTimer();
|
||||||
dismissEasyLoading();
|
dismissEasyLoading();
|
||||||
|
AppLog.log('state.settingOptions.value:${state.settingOptions.value}');
|
||||||
switch (state.settingOptions.value) {
|
switch (state.settingOptions.value) {
|
||||||
case 1: //自动亮屏
|
case 1: //自动亮屏
|
||||||
{
|
{
|
||||||
updateAutoLightScreenConfig();
|
await updateAutoLightScreenConfig();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: //逗留警告
|
case 2: //逗留警告
|
||||||
{
|
{
|
||||||
updateStayWarnConfig();
|
await updateStayWarnConfig();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: //异常警告
|
case 3: //异常警告
|
||||||
{
|
{
|
||||||
updateAbnormalWarnConfig();
|
await updateAbnormalWarnConfig();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 4: //设置亮屏持续时间
|
case 4: //设置亮屏持续时间
|
||||||
{
|
{
|
||||||
updateLightScreenTimeConfig();
|
await updateLightScreenTimeConfig();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 5: //修改猫眼工作模式
|
case 5: //修改猫眼工作模式
|
||||||
{
|
{
|
||||||
updateCatEyeModeConfig();
|
await updateCatEyeModeConfig();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -288,6 +290,7 @@ class CatEyeSetLogic extends BaseGetXController {
|
|||||||
.catEyeConfig![0]
|
.catEyeConfig![0]
|
||||||
.catEyeModeConfig
|
.catEyeModeConfig
|
||||||
?.realTimeMode = state.catEyeConfig.value.realTimeMode;
|
?.realTimeMode = state.catEyeConfig.value.realTimeMode;
|
||||||
|
|
||||||
eventBus
|
eventBus
|
||||||
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||||
}
|
}
|
||||||
@ -456,6 +459,10 @@ class CatEyeSetLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sendBlueMessage() {
|
void sendBlueMessage() {
|
||||||
|
showEasyLoading();
|
||||||
|
showBlueConnetctToastTimer(action: () {
|
||||||
|
dismissEasyLoading();
|
||||||
|
});
|
||||||
final message = _buildCatEyeSetBlueMessage();
|
final message = _buildCatEyeSetBlueMessage();
|
||||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||||
(BluetoothConnectionState connectionState) async {
|
(BluetoothConnectionState connectionState) async {
|
||||||
|
|||||||
@ -80,12 +80,12 @@ class _CatEyeSetPageState extends State<CatEyeSetPage> {
|
|||||||
isHaveRightWidget: true,
|
isHaveRightWidget: true,
|
||||||
rightWidget: _otherToDoSwitch(2),
|
rightWidget: _otherToDoSwitch(2),
|
||||||
)),
|
)),
|
||||||
Obx(() => CommonItem(
|
// Obx(() => CommonItem(
|
||||||
leftTitel: '异常警告'.tr,
|
// leftTitel: '异常警告'.tr,
|
||||||
rightTitle: '',
|
// rightTitle: '',
|
||||||
isHaveLine: true,
|
// isHaveLine: true,
|
||||||
isHaveRightWidget: true,
|
// isHaveRightWidget: true,
|
||||||
rightWidget: _otherToDoSwitch(3))),
|
// rightWidget: _otherToDoSwitch(3))),
|
||||||
//ToDo 需增加国际化
|
//ToDo 需增加国际化
|
||||||
CommonItem(
|
CommonItem(
|
||||||
leftTitel: '呼叫目标'.tr,
|
leftTitel: '呼叫目标'.tr,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import 'package:star_lock/blue/blue_manage.dart';
|
|||||||
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
|
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_otaUpgrade.dart';
|
import 'package:star_lock/blue/io_protocol/io_otaUpgrade.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart';
|
import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart';
|
||||||
|
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
|
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
||||||
@ -55,9 +56,12 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
|
|||||||
handleVoiceConfigureThrottled(reply);
|
handleVoiceConfigureThrottled(reply);
|
||||||
} else if (reply is SetVoicePackageFinalResultReply) {
|
} else if (reply is SetVoicePackageFinalResultReply) {
|
||||||
handleSetResult(reply);
|
handleSetResult(reply);
|
||||||
|
} else if (reply is ReadLockCurrentVoicePacketReply) {
|
||||||
|
handleLockCurrentVoicePacketResult(reply);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await initList();
|
await initList();
|
||||||
|
readLockLanguage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取列表
|
/// 获取列表
|
||||||
@ -435,9 +439,24 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
|
|||||||
_handlerVoicePackageConfigureConfirmation(
|
_handlerVoicePackageConfigureConfirmation(
|
||||||
VoicePackageConfigureConfirmationReply reply,
|
VoicePackageConfigureConfirmationReply reply,
|
||||||
) async {
|
) async {
|
||||||
final int status = reply.data[2];
|
showEasyLoading();
|
||||||
switch (status) {
|
showBlueConnetctToastTimer(action: () {
|
||||||
case 0x00:
|
dismissEasyLoading();
|
||||||
|
});
|
||||||
|
final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre(
|
||||||
|
data: {
|
||||||
|
'lang': state.tempLangStr.value,
|
||||||
|
'timbre': state.tempTimbreStr.value,
|
||||||
|
},
|
||||||
|
lockId: state.lockSetInfoData.value.lockId!,
|
||||||
|
);
|
||||||
|
if (entity.errorCode!.codeIsSuccessful) {
|
||||||
|
showSuccess('设置成功'.tr, something: () async {
|
||||||
|
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang =
|
||||||
|
state.tempLangStr.value;
|
||||||
|
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
|
||||||
|
?.timbre = state.tempTimbreStr.value;
|
||||||
|
|
||||||
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||||
(BluetoothConnectionState deviceConnectionState) async {
|
(BluetoothConnectionState deviceConnectionState) async {
|
||||||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||||
@ -454,10 +473,8 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
|
|||||||
showBlueConnetctToast();
|
showBlueConnetctToast();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
await Future.delayed(Duration(seconds: 1));
|
||||||
default:
|
});
|
||||||
showToast('设置'.tr + '失败'.tr);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,24 +483,6 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
cancelBlueConnetctToastTimer();
|
cancelBlueConnetctToastTimer();
|
||||||
final LoginEntity entity =
|
|
||||||
await ApiRepository.to.settingCurrentVoiceTimbre(
|
|
||||||
data: {
|
|
||||||
'lang': state.tempLangStr.value,
|
|
||||||
'timbre': state.tempTimbreStr.value,
|
|
||||||
},
|
|
||||||
lockId: state.lockSetInfoData.value.lockId!,
|
|
||||||
);
|
|
||||||
if (entity.errorCode!.codeIsSuccessful) {
|
|
||||||
showSuccess('设置成功'.tr, something: () {
|
|
||||||
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
|
|
||||||
?.lang = state.tempLangStr.value;
|
|
||||||
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
|
|
||||||
?.timbre = state.tempTimbreStr.value;
|
|
||||||
eventBus.fire(
|
|
||||||
PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dismissEasyLoading();
|
dismissEasyLoading();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -491,4 +490,74 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleLockCurrentVoicePacketResult(
|
||||||
|
ReadLockCurrentVoicePacketReply reply) {
|
||||||
|
final int status = reply.data[2];
|
||||||
|
switch (status) {
|
||||||
|
case 0x00:
|
||||||
|
//成功
|
||||||
|
cancelBlueConnetctToastTimer();
|
||||||
|
|
||||||
|
const int languageCodeStartIndex = 3;
|
||||||
|
const int languageCodeLength = 20;
|
||||||
|
const int languageCodeEndIndex =
|
||||||
|
languageCodeStartIndex + languageCodeLength; // 23
|
||||||
|
|
||||||
|
if (reply.data.length < languageCodeEndIndex) {
|
||||||
|
throw Exception(
|
||||||
|
'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}');
|
||||||
|
}
|
||||||
|
List<int> languageCodeBytes =
|
||||||
|
reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex);
|
||||||
|
|
||||||
|
String languageCode = String.fromCharCodes(languageCodeBytes);
|
||||||
|
|
||||||
|
languageCode = languageCode.trim(); // 移除首尾空格
|
||||||
|
languageCode =
|
||||||
|
languageCode.replaceAll('\u0000', ''); // 移除空字符 (null bytes)
|
||||||
|
|
||||||
|
if (languageCode != null && languageCode != '') {
|
||||||
|
final indexWhere = state.languages
|
||||||
|
.indexWhere((element) => element.lang == languageCode);
|
||||||
|
if (indexWhere != -1) {
|
||||||
|
print('锁板上的语言是:$languageCode,下标是:$indexWhere');
|
||||||
|
state.selectPassthroughListIndex.value = indexWhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dismissEasyLoading();
|
||||||
|
break;
|
||||||
|
case 0x06:
|
||||||
|
//无权限
|
||||||
|
final List<int> token = reply.data.sublist(2, 6);
|
||||||
|
if (state.data != null) {
|
||||||
|
sendFileToDevice(state.data!, token);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void readLockLanguage() async {
|
||||||
|
showEasyLoading();
|
||||||
|
showBlueConnetctToastTimer(action: () {
|
||||||
|
dismissEasyLoading();
|
||||||
|
});
|
||||||
|
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||||
|
(BluetoothConnectionState deviceConnectionState) async {
|
||||||
|
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||||
|
await BlueManage().writeCharacteristicWithResponse(
|
||||||
|
ReadLockCurrentVoicePacket(
|
||||||
|
lockID: BlueManage().connectDeviceName,
|
||||||
|
).packageData(),
|
||||||
|
);
|
||||||
|
} else if (deviceConnectionState ==
|
||||||
|
BluetoothConnectionState.disconnected) {
|
||||||
|
dismissEasyLoading();
|
||||||
|
cancelBlueConnetctToastTimer();
|
||||||
|
showBlueConnetctToast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,6 +63,12 @@ class _SpeechLanguageSettingsPageState
|
|||||||
final soundType = state.soundTypeList.value[index];
|
final soundType = state.soundTypeList.value[index];
|
||||||
return CommonItem(
|
return CommonItem(
|
||||||
leftTitel: soundType,
|
leftTitel: soundType,
|
||||||
|
leftTitleStyle: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
fontWeight: state.selectSoundTypeIndex.value == index
|
||||||
|
? FontWeight.bold
|
||||||
|
: null,
|
||||||
|
),
|
||||||
rightTitle: '',
|
rightTitle: '',
|
||||||
isHaveLine: !isLastItem,
|
isHaveLine: !isLastItem,
|
||||||
isHaveDirection: false,
|
isHaveDirection: false,
|
||||||
@ -94,35 +100,44 @@ class _SpeechLanguageSettingsPageState
|
|||||||
height: 8.h,
|
height: 8.h,
|
||||||
),
|
),
|
||||||
// 语言包列表区
|
// 语言包列表区
|
||||||
Container(
|
Obx(
|
||||||
color: Colors.transparent,
|
() => Container(
|
||||||
child: Column(
|
color: Colors.transparent,
|
||||||
children: List.generate(
|
child: Column(
|
||||||
state.languages.length,
|
children: List.generate(
|
||||||
(index) {
|
state.languages.length,
|
||||||
final item = state.languages[index];
|
(index) {
|
||||||
return CommonItem(
|
final item = state.languages[index];
|
||||||
leftTitel: item.langText,
|
return CommonItem(
|
||||||
rightTitle: '',
|
leftTitel: item.langText,
|
||||||
isHaveLine: true,
|
leftTitleStyle: TextStyle(
|
||||||
isHaveDirection: false,
|
fontSize: 20.sp,
|
||||||
isHaveRightWidget: true,
|
fontWeight: state.selectPassthroughListIndex.value == index
|
||||||
leftTitleMaxWidth: 0.9.sw, // 设置左侧标题最大宽度
|
? FontWeight.bold
|
||||||
rightWidget:
|
: null,
|
||||||
state.selectPassthroughListIndex.value == index
|
),
|
||||||
? Image(
|
rightTitle: '',
|
||||||
image: const AssetImage(
|
isHaveLine: true,
|
||||||
'images/icon_item_checked.png'),
|
isHaveDirection: false,
|
||||||
width: 30.w,
|
isHaveRightWidget: true,
|
||||||
height: 30.w,
|
leftTitleMaxWidth: 0.9.sw,
|
||||||
fit: BoxFit.contain,
|
// 设置左侧标题最大宽度
|
||||||
)
|
rightWidget:
|
||||||
: Container(),
|
state.selectPassthroughListIndex.value == index
|
||||||
action: () {
|
? Image(
|
||||||
state.selectPassthroughListIndex.value = index;
|
image: const AssetImage(
|
||||||
},
|
'images/icon_item_checked.png'),
|
||||||
);
|
width: 30.w,
|
||||||
},
|
height: 30.w,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
action: () {
|
||||||
|
state.selectPassthroughListIndex.value = index;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -133,16 +148,6 @@ class _SpeechLanguageSettingsPageState
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildList() {
|
|
||||||
final appLocalLanguages = state.languages;
|
|
||||||
return List.generate(
|
|
||||||
appLocalLanguages.length,
|
|
||||||
(index) => _buildItem(
|
|
||||||
appLocalLanguages[index],
|
|
||||||
index,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -152,24 +157,4 @@ class _SpeechLanguageSettingsPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildItem(PassthroughItem item, index) {
|
|
||||||
return CommonItem(
|
|
||||||
leftTitel: item.langText,
|
|
||||||
rightTitle: '',
|
|
||||||
isHaveLine: true,
|
|
||||||
isHaveDirection: false,
|
|
||||||
isHaveRightWidget: true,
|
|
||||||
rightWidget: state.selectPassthroughListIndex.value == index
|
|
||||||
? Image(
|
|
||||||
image: const AssetImage('images/icon_item_checked.png'),
|
|
||||||
width: 30.w,
|
|
||||||
height: 30.w,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
action: () {
|
|
||||||
state.selectPassthroughListIndex.value = index;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import 'package:star_lock/app_settings/app_colors.dart';
|
|||||||
import 'package:star_lock/blue/blue_manage.dart';
|
import 'package:star_lock/blue/blue_manage.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
|
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
|
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
|
||||||
|
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
|
||||||
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
|
||||||
import 'package:star_lock/blue/io_reply.dart';
|
import 'package:star_lock/blue/io_reply.dart';
|
||||||
@ -53,6 +54,8 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
handleVoiceConfigureThrottled(reply);
|
handleVoiceConfigureThrottled(reply);
|
||||||
} else if (reply is ReadLockCurrentVoicePacketReply) {
|
} else if (reply is ReadLockCurrentVoicePacketReply) {
|
||||||
handleLockCurrentVoicePacketResult(reply);
|
handleLockCurrentVoicePacketResult(reply);
|
||||||
|
} else if (reply is SetVoicePackageFinalResultReply) {
|
||||||
|
handleSetResult(reply);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
initList();
|
initList();
|
||||||
@ -77,6 +80,10 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
|
|
||||||
Future<void> _executeLogic(
|
Future<void> _executeLogic(
|
||||||
VoicePackageConfigureConfirmationReply reply) async {
|
VoicePackageConfigureConfirmationReply reply) async {
|
||||||
|
showEasyLoading();
|
||||||
|
showBlueConnetctToastTimer(action: () {
|
||||||
|
dismissEasyLoading();
|
||||||
|
});
|
||||||
final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre(
|
final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre(
|
||||||
data: {
|
data: {
|
||||||
'lang': state.tempLangStr.value,
|
'lang': state.tempLangStr.value,
|
||||||
@ -85,18 +92,47 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
lockId: state.lockSetInfoData.value.lockId!,
|
lockId: state.lockSetInfoData.value.lockId!,
|
||||||
);
|
);
|
||||||
if (entity.errorCode!.codeIsSuccessful) {
|
if (entity.errorCode!.codeIsSuccessful) {
|
||||||
showSuccess('设置成功'.tr, something: () {
|
showSuccess('设置成功'.tr, something: () async {
|
||||||
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang =
|
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang =
|
||||||
state.tempLangStr.value;
|
state.tempLangStr.value;
|
||||||
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
|
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
|
||||||
?.timbre = state.tempTimbreStr.value;
|
?.timbre = state.tempTimbreStr.value;
|
||||||
|
|
||||||
|
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||||
|
(BluetoothConnectionState deviceConnectionState) async {
|
||||||
|
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||||
|
await BlueManage().writeCharacteristicWithResponse(
|
||||||
|
SetVoicePackageFinalResult(
|
||||||
|
lockID: BlueManage().connectDeviceName,
|
||||||
|
languageCode: state.tempLangStr.value,
|
||||||
|
).packageData(),
|
||||||
|
);
|
||||||
|
} else if (deviceConnectionState ==
|
||||||
|
BluetoothConnectionState.disconnected) {
|
||||||
|
dismissEasyLoading();
|
||||||
|
cancelBlueConnetctToastTimer();
|
||||||
|
showBlueConnetctToast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
eventBus
|
eventBus
|
||||||
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||||
Get.offAllNamed(Routers.starLockMain);
|
Get.offAllNamed(Routers.starLockMain);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dismissEasyLoading();
|
}
|
||||||
|
|
||||||
|
void handleSetResult(SetVoicePackageFinalResultReply reply) async {
|
||||||
|
final int status = reply.data[2];
|
||||||
|
switch (status) {
|
||||||
|
case 0x00:
|
||||||
|
cancelBlueConnetctToastTimer();
|
||||||
|
dismissEasyLoading();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showToast('设置'.tr + '失败'.tr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveSpeechLanguageSettings() async {
|
void saveSpeechLanguageSettings() async {
|
||||||
@ -201,14 +237,12 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
// 开始配置语音包
|
// 开始配置语音包
|
||||||
void _handlerStartVoicePackageConfigure(
|
void _handlerStartVoicePackageConfigure(
|
||||||
VoicePackageConfigureReply reply) async {
|
VoicePackageConfigureReply reply) async {
|
||||||
final int status = reply.data[3];
|
final int status = reply.data[6];
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
//成功
|
//成功
|
||||||
cancelBlueConnetctToastTimer();
|
cancelBlueConnetctToastTimer();
|
||||||
|
|
||||||
_startSendLanguageFile();
|
_startSendLanguageFile();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 0x06:
|
case 0x06:
|
||||||
//无权限
|
//无权限
|
||||||
@ -218,7 +252,8 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showToast('获取设备型号失败'.tr);
|
dismissEasyLoading();
|
||||||
|
cancelBlueConnetctToastTimer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,6 +444,10 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void readLockLanguage() async {
|
void readLockLanguage() async {
|
||||||
|
showEasyLoading();
|
||||||
|
showBlueConnetctToastTimer(action: () {
|
||||||
|
dismissEasyLoading();
|
||||||
|
});
|
||||||
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
await BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||||
(BluetoothConnectionState deviceConnectionState) async {
|
(BluetoothConnectionState deviceConnectionState) async {
|
||||||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||||||
@ -428,40 +467,42 @@ class LockVoiceSettingLogic extends BaseGetXController {
|
|||||||
|
|
||||||
void handleLockCurrentVoicePacketResult(
|
void handleLockCurrentVoicePacketResult(
|
||||||
ReadLockCurrentVoicePacketReply reply) {
|
ReadLockCurrentVoicePacketReply reply) {
|
||||||
final int status = reply.data[6];
|
final int status = reply.data[2];
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
//成功
|
//成功
|
||||||
cancelBlueConnetctToastTimer();
|
cancelBlueConnetctToastTimer();
|
||||||
|
|
||||||
// 1. 计算 LanguageCode 在字节数组中的起始和结束索引
|
|
||||||
// CmdID (2 bytes) + Status (1 byte) = 3 bytes -> LanguageCode 从索引 3 开始
|
|
||||||
const int languageCodeStartIndex = 3;
|
const int languageCodeStartIndex = 3;
|
||||||
const int languageCodeLength = 20;
|
const int languageCodeLength = 20;
|
||||||
const int languageCodeEndIndex =
|
const int languageCodeEndIndex =
|
||||||
languageCodeStartIndex + languageCodeLength; // 23
|
languageCodeStartIndex + languageCodeLength; // 23
|
||||||
|
|
||||||
// 2. 检查数据长度是否足够
|
|
||||||
if (reply.data.length < languageCodeEndIndex) {
|
if (reply.data.length < languageCodeEndIndex) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}');
|
'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}');
|
||||||
}
|
}
|
||||||
// 3. 从字节数组中截取 LanguageCode 对应的字节段
|
|
||||||
List<int> languageCodeBytes =
|
List<int> languageCodeBytes =
|
||||||
reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex);
|
reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex);
|
||||||
|
|
||||||
// 4. 将字节列表转换为字符串
|
|
||||||
// 通常这种编码是 UTF-8 或 ASCII
|
|
||||||
String languageCode = String.fromCharCodes(languageCodeBytes);
|
String languageCode = String.fromCharCodes(languageCodeBytes);
|
||||||
|
|
||||||
// 5. (可选) 清理字符串:移除可能的填充字符(如空字符 '\0' 或空格)
|
|
||||||
// 因为字段长度固定为20,不足的部分可能用 '\0' 填充
|
|
||||||
languageCode = languageCode.trim(); // 移除首尾空格
|
languageCode = languageCode.trim(); // 移除首尾空格
|
||||||
languageCode =
|
languageCode =
|
||||||
languageCode.replaceAll('\u0000', ''); // 移除空字符 (null bytes)
|
languageCode.replaceAll('\u0000', ''); // 移除空字符 (null bytes)
|
||||||
|
|
||||||
// 6. 使用提取到的 languageCode
|
|
||||||
print('LanguageCode: $languageCode'); // 例如: zh_CN, en_US
|
print('LanguageCode: $languageCode'); // 例如: zh_CN, en_US
|
||||||
|
|
||||||
|
if (languageCode != null && languageCode != '') {
|
||||||
|
final indexWhere = state.languages
|
||||||
|
.indexWhere((element) => element.lang == languageCode);
|
||||||
|
if (indexWhere != -1) {
|
||||||
|
print('锁板上的语言是:$languageCode,下标是:$indexWhere');
|
||||||
|
state.selectPassthroughListIndex.value = indexWhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dismissEasyLoading();
|
||||||
break;
|
break;
|
||||||
case 0x06:
|
case 0x06:
|
||||||
//无权限
|
//无权限
|
||||||
|
|||||||
@ -96,7 +96,7 @@ class _LockVoiceSettingState extends State<LockVoiceSetting> {
|
|||||||
return CommonItem(
|
return CommonItem(
|
||||||
leftTitel: soundType,
|
leftTitel: soundType,
|
||||||
leftTitleStyle: TextStyle(
|
leftTitleStyle: TextStyle(
|
||||||
fontSize: 22.sp,
|
fontSize: 20.sp,
|
||||||
fontWeight: state.selectSoundTypeIndex.value == index
|
fontWeight: state.selectSoundTypeIndex.value == index
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: null,
|
: null,
|
||||||
@ -142,7 +142,7 @@ class _LockVoiceSettingState extends State<LockVoiceSetting> {
|
|||||||
return CommonItem(
|
return CommonItem(
|
||||||
leftTitel: item.langText,
|
leftTitel: item.langText,
|
||||||
leftTitleStyle: TextStyle(
|
leftTitleStyle: TextStyle(
|
||||||
fontSize: 22.sp,
|
fontSize: 20.sp,
|
||||||
fontWeight:
|
fontWeight:
|
||||||
state.selectPassthroughListIndex.value == index
|
state.selectPassthroughListIndex.value == index
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
@ -152,7 +152,7 @@ class _LockVoiceSettingState extends State<LockVoiceSetting> {
|
|||||||
isHaveLine: true,
|
isHaveLine: true,
|
||||||
isHaveDirection: false,
|
isHaveDirection: false,
|
||||||
isHaveRightWidget: true,
|
isHaveRightWidget: true,
|
||||||
leftTitleMaxWidth: 0.9.sw,
|
leftTitleMaxWidth: 0.85.sw,
|
||||||
rightWidget:
|
rightWidget:
|
||||||
state.selectPassthroughListIndex.value == index
|
state.selectPassthroughListIndex.value == index
|
||||||
? Image(
|
? Image(
|
||||||
|
|||||||
@ -110,22 +110,8 @@ class ImageTransmissionLogic extends BaseGetXController {
|
|||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
// // 第一帧到达时记录开始时间
|
// 没有开启所有和录音时不缓存和播放音频
|
||||||
if (_isFirstAudioFrame) {
|
if (!state.isOpenVoice.value && state.isRecordingAudio.value) {
|
||||||
_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;
|
return;
|
||||||
}
|
}
|
||||||
if (state.audioBuffer.length >= audioBufferSize) {
|
if (state.audioBuffer.length >= audioBufferSize) {
|
||||||
@ -212,7 +198,8 @@ class ImageTransmissionLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 播放音频数据
|
/// 播放音频数据
|
||||||
void _playAudioData(TalkData talkData) async {
|
void _playAudioData(TalkData talkData) async {
|
||||||
if (state.isOpenVoice.value) {
|
if (state.isOpenVoice.value &&
|
||||||
|
state.isRecordingAudio.value == false) {
|
||||||
final list =
|
final list =
|
||||||
G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150);
|
G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150);
|
||||||
// // 将 PCM 数据转换为 PcmArrayInt16
|
// // 将 PCM 数据转换为 PcmArrayInt16
|
||||||
@ -565,7 +552,6 @@ class ImageTransmissionLogic extends BaseGetXController {
|
|||||||
|
|
||||||
//开始录音
|
//开始录音
|
||||||
Future<void> startProcessingAudio() async {
|
Future<void> startProcessingAudio() async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
||||||
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
||||||
@ -602,12 +588,23 @@ class ImageTransmissionLogic extends BaseGetXController {
|
|||||||
} on PlatformException catch (ex) {
|
} on PlatformException catch (ex) {
|
||||||
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
||||||
} finally {
|
} finally {
|
||||||
|
// 延迟关闭定时器,确保剩余数据能发送出去
|
||||||
|
if (_startProcessingAudioTimer != null) {
|
||||||
|
// 插入5个320长度的全0数据包
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
|
||||||
|
}
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
_startProcessingAudioTimer?.cancel();
|
||||||
|
_startProcessingAudioTimer = null;
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
}
|
||||||
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
||||||
state.isRecordingAudio.value = isRecording!;
|
state.isRecordingAudio.value = isRecording!;
|
||||||
}
|
}
|
||||||
_startProcessingAudioTimer?.cancel();
|
|
||||||
_startProcessingAudioTimer = null;
|
|
||||||
_bufferedAudioFrames.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
||||||
@ -645,38 +642,24 @@ class ImageTransmissionLogic extends BaseGetXController {
|
|||||||
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
||||||
_bufferedAudioFrames.addAll(encodedData);
|
_bufferedAudioFrames.addAll(encodedData);
|
||||||
|
|
||||||
|
|
||||||
// 启动定时发送器(仅启动一次)
|
// 启动定时发送器(仅启动一次)
|
||||||
if (_startProcessingAudioTimer == null && _bufferedAudioFrames.length > chunkSize) {
|
if (_startProcessingAudioTimer == null &&
|
||||||
_startProcessingAudioTimer = Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
_bufferedAudioFrames.length > chunkSize) {
|
||||||
|
_startProcessingAudioTimer =
|
||||||
|
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 错误监听
|
// 错误监听
|
||||||
void _onError(VoiceProcessorException error) {
|
void _onError(VoiceProcessorException error) {
|
||||||
AppLog.log(error.message!);
|
AppLog.log(error.message!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加音频增益处理方法
|
|
||||||
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
||||||
List<int> result = List<int>.filled(pcmData.length, 0);
|
return pcmData.map((sample) {
|
||||||
|
// 增益并裁剪
|
||||||
for (int i = 0; i < pcmData.length; i++) {
|
int amplified = (sample * gainFactor).round();
|
||||||
// PCM数据通常是有符号的16位整数
|
return amplified.clamp(-32768, 32767);
|
||||||
int sample = pcmData[i];
|
}).toList();
|
||||||
|
|
||||||
// 应用增益
|
|
||||||
double amplified = sample * gainFactor;
|
|
||||||
|
|
||||||
// 限制在有效范围内,防止溢出
|
|
||||||
if (amplified > 32767) {
|
|
||||||
amplified = 32767;
|
|
||||||
} else if (amplified < -32768) {
|
|
||||||
amplified = -32768;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[i] = amplified.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,10 +104,10 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
codecType: 'h264',
|
codecType: 'h264',
|
||||||
);
|
);
|
||||||
// 初始化解码器并获取textureId
|
// 初始化解码器并获取textureId
|
||||||
AppLog.log('StartChartManage().videoWidth:${StartChartManage()
|
AppLog.log(
|
||||||
.videoWidth}');
|
'StartChartManage().videoWidth:${StartChartManage().videoWidth}');
|
||||||
AppLog.log('StartChartManage().videoHeight:${StartChartManage()
|
AppLog.log(
|
||||||
.videoHeight}');
|
'StartChartManage().videoHeight:${StartChartManage().videoHeight}');
|
||||||
final textureId = await VideoDecodePlugin.initDecoder(config);
|
final textureId = await VideoDecodePlugin.initDecoder(config);
|
||||||
if (textureId != null) {
|
if (textureId != null) {
|
||||||
Future.microtask(() => state.textureId.value = textureId);
|
Future.microtask(() => state.textureId.value = textureId);
|
||||||
@ -493,7 +493,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 播放音频数据
|
/// 播放音频数据
|
||||||
void _playAudioData(TalkData talkData) async {
|
void _playAudioData(TalkData talkData) async {
|
||||||
if (state.isOpenVoice.value && state.isLoading.isFalse) {
|
if (state.isOpenVoice.value &&
|
||||||
|
state.isLoading.isFalse &&
|
||||||
|
state.isRecordingAudio.value == false) {
|
||||||
List<int> encodedData = G711Tool.decode(talkData.content, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.decode(talkData.content, 0); // 0表示A-law
|
||||||
// 将 PCM 数据转换为 PcmArrayInt16
|
// 将 PCM 数据转换为 PcmArrayInt16
|
||||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(encodedData);
|
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(encodedData);
|
||||||
@ -746,7 +748,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
|
|
||||||
//开始录音
|
//开始录音
|
||||||
Future<void> startProcessingAudio() async {
|
Future<void> startProcessingAudio() async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
||||||
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
||||||
@ -783,39 +784,36 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
} on PlatformException catch (ex) {
|
} on PlatformException catch (ex) {
|
||||||
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
||||||
} finally {
|
} finally {
|
||||||
|
// 延迟关闭定时器,确保剩余数据能发送出去
|
||||||
|
if (_startProcessingAudioTimer != null) {
|
||||||
|
// 插入5个320长度的全0数据包
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
|
||||||
|
}
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
_startProcessingAudioTimer?.cancel();
|
||||||
|
_startProcessingAudioTimer = null;
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
}
|
||||||
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
||||||
state.isRecordingAudio.value = isRecording!;
|
state.isRecordingAudio.value = isRecording!;
|
||||||
}
|
}
|
||||||
_startProcessingAudioTimer?.cancel();
|
|
||||||
_startProcessingAudioTimer = null;
|
|
||||||
_bufferedAudioFrames.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加音频增益处理方法
|
// 添加音频增益处理方法
|
||||||
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
||||||
List<int> result = List<int>.filled(pcmData.length, 0);
|
return pcmData.map((sample) {
|
||||||
|
// 增益并裁剪
|
||||||
for (int i = 0; i < pcmData.length; i++) {
|
int amplified = (sample * gainFactor).round();
|
||||||
// PCM数据通常是有符号的16位整数
|
return amplified.clamp(-32768, 32767);
|
||||||
int sample = pcmData[i];
|
}).toList();
|
||||||
|
|
||||||
// 应用增益
|
|
||||||
double amplified = sample * gainFactor;
|
|
||||||
|
|
||||||
// 限制在有效范围内,防止溢出
|
|
||||||
if (amplified > 32767) {
|
|
||||||
amplified = 32767;
|
|
||||||
} else if (amplified < -32768) {
|
|
||||||
amplified = -32768;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[i] = amplified.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
||||||
static const int intervalMs = 40; // 每40ms发送一次(4个chunk)
|
static const int intervalMs = 35; // 每40ms发送一次(4个chunk)
|
||||||
void _sendAudioChunk(Timer timer) async {
|
void _sendAudioChunk(Timer timer) async {
|
||||||
if (_bufferedAudioFrames.length < chunkSize) {
|
if (_bufferedAudioFrames.length < chunkSize) {
|
||||||
// 数据不足,等待下一周期
|
// 数据不足,等待下一周期
|
||||||
@ -849,10 +847,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
||||||
_bufferedAudioFrames.addAll(encodedData);
|
_bufferedAudioFrames.addAll(encodedData);
|
||||||
|
|
||||||
|
|
||||||
// 启动定时发送器(仅启动一次)
|
// 启动定时发送器(仅启动一次)
|
||||||
if (_startProcessingAudioTimer == null && _bufferedAudioFrames.length > chunkSize) {
|
if (_startProcessingAudioTimer == null &&
|
||||||
_startProcessingAudioTimer = Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
_bufferedAudioFrames.length > chunkSize) {
|
||||||
|
_startProcessingAudioTimer =
|
||||||
|
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,7 +972,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
if (!state.isOpenVoice.value) {
|
// 没有开启所有和录音时不缓存和播放音频
|
||||||
|
if (!state.isOpenVoice.value || state.isRecordingAudio.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.audioBuffer.length >= audioBufferSize) {
|
if (state.audioBuffer.length >= audioBufferSize) {
|
||||||
|
|||||||
@ -109,22 +109,10 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
// // 第一帧到达时记录开始时间
|
// 没有开启所有和录音时不缓存和播放音频
|
||||||
if (_isFirstAudioFrame) {
|
if (!state.isOpenVoice.value || state.isRecordingAudio.value) {
|
||||||
_startAudioTime = currentTime;
|
print(
|
||||||
_isFirstAudioFrame = false;
|
'录音时丢弃数据:${state.isOpenVoice.value}-${state.isRecordingAudio.value}');
|
||||||
}
|
|
||||||
|
|
||||||
// 计算音频延迟
|
|
||||||
final expectedTime = _startAudioTime + talkData.durationMs;
|
|
||||||
final audioDelay = currentTime - expectedTime;
|
|
||||||
|
|
||||||
// 如果延迟太大,清空缓冲区并直接播放
|
|
||||||
if (audioDelay > 500) {
|
|
||||||
state.audioBuffer.clear();
|
|
||||||
if (state.isOpenVoice.value) {
|
|
||||||
_playAudioFrames();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.audioBuffer.length >= audioBufferSize) {
|
if (state.audioBuffer.length >= audioBufferSize) {
|
||||||
@ -388,9 +376,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
if (state.videoBuffer.isNotEmpty) {
|
if (state.videoBuffer.isNotEmpty) {
|
||||||
final TalkData oldestFrame = state.videoBuffer.removeAt(0);
|
final TalkData oldestFrame = state.videoBuffer.removeAt(0);
|
||||||
if (oldestFrame.content.isNotEmpty) {
|
if (oldestFrame.content.isNotEmpty) {
|
||||||
state.listData.value = Uint8List.fromList(oldestFrame.content); // 备份原始数据
|
state.listData.value =
|
||||||
|
Uint8List.fromList(oldestFrame.content); // 备份原始数据
|
||||||
final int decodeStart = DateTime.now().millisecondsSinceEpoch;
|
final int decodeStart = DateTime.now().millisecondsSinceEpoch;
|
||||||
decodeImageFromList(Uint8List.fromList(oldestFrame.content)).then((ui.Image img) {
|
decodeImageFromList(Uint8List.fromList(oldestFrame.content))
|
||||||
|
.then((ui.Image img) {
|
||||||
final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
|
final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
|
||||||
state.currentImage.value = img;
|
state.currentImage.value = img;
|
||||||
_renderedFrameCount++;
|
_renderedFrameCount++;
|
||||||
@ -524,7 +514,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
final lockPeerId = StartChartManage().lockPeerId;
|
final lockPeerId = StartChartManage().lockPeerId;
|
||||||
final LockListInfoGroupEntity? lockListInfoGroupEntity =
|
final LockListInfoGroupEntity? lockListInfoGroupEntity =
|
||||||
await Storage.getLockMainListData();
|
await Storage.getLockMainListData();
|
||||||
if (lockListInfoGroupEntity != null) {
|
if (lockListInfoGroupEntity != null) {
|
||||||
lockListInfoGroupEntity!.groupList?.forEach((element) {
|
lockListInfoGroupEntity!.groupList?.forEach((element) {
|
||||||
final lockList = element.lockList;
|
final lockList = element.lockList;
|
||||||
@ -562,7 +552,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
//开始录音
|
//开始录音
|
||||||
Future<void> startProcessingAudio() async {
|
Future<void> startProcessingAudio() async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
||||||
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
||||||
@ -599,12 +588,23 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
} on PlatformException catch (ex) {
|
} on PlatformException catch (ex) {
|
||||||
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
// state.errorMessage.value = 'Failed to stop recorder: $ex';
|
||||||
} finally {
|
} finally {
|
||||||
|
// 延迟关闭定时器,确保剩余数据能发送出去
|
||||||
|
if (_startProcessingAudioTimer != null) {
|
||||||
|
// 插入5个320长度的全0数据包
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
|
||||||
|
}
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
_startProcessingAudioTimer?.cancel();
|
||||||
|
_startProcessingAudioTimer = null;
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_bufferedAudioFrames.clear();
|
||||||
|
}
|
||||||
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
||||||
state.isRecordingAudio.value = isRecording!;
|
state.isRecordingAudio.value = isRecording!;
|
||||||
}
|
}
|
||||||
_startProcessingAudioTimer?.cancel();
|
|
||||||
_startProcessingAudioTimer = null;
|
|
||||||
_bufferedAudioFrames.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
static const int chunkSize = 320; // 每次发送320字节(10ms G.711)
|
||||||
@ -642,10 +642,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
||||||
_bufferedAudioFrames.addAll(encodedData);
|
_bufferedAudioFrames.addAll(encodedData);
|
||||||
|
|
||||||
|
|
||||||
// 启动定时发送器(仅启动一次)
|
// 启动定时发送器(仅启动一次)
|
||||||
if (_startProcessingAudioTimer == null && _bufferedAudioFrames.length > chunkSize) {
|
if (_startProcessingAudioTimer == null &&
|
||||||
_startProcessingAudioTimer = Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
_bufferedAudioFrames.length > chunkSize) {
|
||||||
|
_startProcessingAudioTimer =
|
||||||
|
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,25 +657,10 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
// 添加音频增益处理方法
|
// 添加音频增益处理方法
|
||||||
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
||||||
List<int> result = List<int>.filled(pcmData.length, 0);
|
return pcmData.map((sample) {
|
||||||
|
// 增益并裁剪
|
||||||
for (int i = 0; i < pcmData.length; i++) {
|
int amplified = (sample * gainFactor).round();
|
||||||
// PCM数据通常是有符号的16位整数
|
return amplified.clamp(-32768, 32767);
|
||||||
int sample = pcmData[i];
|
}).toList();
|
||||||
|
|
||||||
// 应用增益
|
|
||||||
double amplified = sample * gainFactor;
|
|
||||||
|
|
||||||
// 限制在有效范围内,防止溢出
|
|
||||||
if (amplified > 32767) {
|
|
||||||
amplified = 32767;
|
|
||||||
} else if (amplified < -32768) {
|
|
||||||
amplified = -32768;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[i] = amplified.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user