Browse Source

Merge branch 'new_dev' of github.com:deepcloud2048/deep_voice into new_dev

weicu
lxm 11 months ago
parent
commit
7d33665e83
  1. 2
      lib/data/models/appconfig_model.dart
  2. 2
      lib/data/models/appconfig_model.g.dart
  3. 18
      lib/data/services/ota_check_service.dart
  4. 2
      lib/modules/home/controllers/home_controller.dart
  5. 13
      lib/modules/ota_upgrade/controllers/ota_core_contropller.dart
  6. 7
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  7. 2
      lib/modules/pairing/controllers/pairing_controller.dart
  8. 125
      lib/modules/translation/controllers/translation_controller.dart
  9. 34
      lib/modules/translation/controllers/translation_history_controller.dart
  10. 25
      lib/modules/translation/views/translation_view.dart
  11. 130
      lib/modules/translation/views/widgets/face_to_face_layout.dart
  12. 10
      lib/modules/translation/views/widgets/translation_item_widget.dart
  13. 10
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt
  14. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  15. 51
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift

2
lib/data/models/appconfig_model.dart

@ -79,6 +79,7 @@ class DBProduct {
final String? productimage;
final String scanuuid;
final String? version;
bool? isforceupdate;
final bool? iscontinuouschat;
final String? updatedescription;
final String? updatepackageaddress;
@ -91,6 +92,7 @@ class DBProduct {
required this.productimage,
required this.scanuuid,
required this.version,
required this.isforceupdate, //该产品固件版本是否强制更新
required this.iscontinuouschat,
required this.updatedescription,
required this.updatepackageaddress,

2
lib/data/models/appconfig_model.g.dart

@ -79,6 +79,7 @@ DBProduct _$DBProductFromJson(Map<String, dynamic> json) => DBProduct(
productimage: json['productimage'] as String?,
scanuuid: json['scanuuid'] as String,
version: json['version'] as String?,
isforceupdate: json['isforceupdate'] as bool?,
iscontinuouschat: json['iscontinuouschat'] as bool?,
updatedescription: json['updatedescription'] as String?,
updatepackageaddress: json['updatepackageaddress'] as String?,
@ -91,6 +92,7 @@ Map<String, dynamic> _$DBProductToJson(DBProduct instance) => <String, dynamic>{
'productimage': instance.productimage,
'scanuuid': instance.scanuuid,
'version': instance.version,
'isforceupdate': instance.isforceupdate,
'iscontinuouschat': instance.iscontinuouschat,
'devicename': instance.devicename,
'updatedescription': instance.updatedescription,

18
lib/data/services/ota_check_service.dart

@ -57,6 +57,7 @@ class OtaCheckService extends GetxService {
/// [isFullyConnected] 设备是否完全连接
/// 返回是否成功执行了检查
Future<bool> performOtaCheck(bool isFullyConnected) async {
bool isForceUpdate = false;
try {
// 检查是否应该进行OTA检查
if (!shouldCheckOta()) {
@ -95,7 +96,12 @@ class OtaCheckService extends GetxService {
_otaCoreService.isNewVersionAvailable(currentVersion, latestVersion);
if (hasUpdate) {
Logger.d(_tag, '发现新版本,准备显示升级提醒');
isForceUpdate = latestInfo['isForceUpdate'] as bool? ?? false;
if (isForceUpdate) {
Logger.d(_tag, '发现强制更新版本,准备显示升级提醒');
} else {
Logger.d(_tag, '发现新版本,准备显示升级提醒');
}
// 再次确认当前在Home页面(防止异步执行期间页面切换)
if (_isCurrentlyOnHomePage()) {
@ -109,12 +115,18 @@ class OtaCheckService extends GetxService {
}
// 标记为已检查(无论是否有更新都标记为已检查)
_markAsChecked();
if (!isForceUpdate) {
_markAsChecked();
}
_isCheckingInProgress = false;
return true;
} catch (e) {
Logger.e(_tag, '检查OTA升级失败: $e');
// 即使检查失败,也标记为已检查,避免重复尝试
_markAsChecked();
if (!isForceUpdate) {
_markAsChecked();
}
_isCheckingInProgress = false;
return false;
}
}

2
lib/modules/home/controllers/home_controller.dart

@ -277,7 +277,7 @@ class HomeController extends GetxController {
// 连接成功后检查OTA升级
if (noviceGuideService.isHomeGuide) {
Future.delayed(const Duration(seconds: 2), () {
Future.delayed(const Duration(seconds: 1), () {
_checkOtaUpgrade();
});
}

13
lib/modules/ota_upgrade/controllers/ota_core_contropller.dart

@ -525,17 +525,20 @@ class OtaCoreService {
}
}
product.isforceupdate = product.version?.contains("+");
return {
'latestVersion': product.version ?? "",
'latestVersion': product.version?.split('+').first ?? "",
'updateDescription': product.updatedescription ?? "",
'updatePackageAddress': product.updatepackageaddress ?? "",
'isForceUpdate': product.isforceupdate ?? false,
};
}
/// 比较版本号,判断是否有新版本可用
/// [currentVersion] 当前版本号
/// [latestVersion] 最新版本号
/// 返回true表示有新版本可用(支持升级和降级)
/// 返回true表示有新版本可用(只支持升级)
bool isNewVersionAvailable(String currentVersion, String latestVersion) {
try {
List<int> currentParts =
@ -555,10 +558,12 @@ class OtaCoreService {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
// 从高位到低位比较版本号
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
if (latestParts[i] > currentParts[i]) {
return true;
} else if (latestParts[i] < currentParts[i]) {
return false;
}
}

7
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -13,7 +13,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
return WillPopScope(
onWillPop: () async {
if (controller.isUpgrade.value) {
if (controller.isUpgrade.value && controller.showDetailedStatus.value) {
// 显示确认弹窗
await Get.dialog(
AlertDialog(
@ -54,7 +54,8 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
color: isDarkMode ? Colors.white : Colors.black,
),
onPressed: () {
if (controller.isUpgrade.value) {
if (controller.isUpgrade.value &&
controller.showDetailedStatus.value) {
// 显示确认弹窗
Get.dialog(
AlertDialog(
@ -827,7 +828,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
// 更新按钮
Widget _buildUpdateButton(bool isDarkMode) {
return Obx(() {
if (controller.isUpgrade.value) {
if (controller.isUpgrade.value && controller.showDetailedStatus.value) {
return _buildProgressIndicator(isDarkMode);
}

2
lib/modules/pairing/controllers/pairing_controller.dart

@ -512,7 +512,7 @@ class PairingController extends GetxController with WidgetsBindingObserver {
/// 开始初始检查
void _startInitialCheck() {
_transition(PairingState.checking);
// _transition(PairingState.checking);
_checkCurrentStatus();
}

125
lib/modules/translation/controllers/translation_controller.dart

@ -234,6 +234,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
Future<void> changeTranslationMode(String mode) async {
currentMode.value = mode;
currentModeTitle.value = _getModeTitle(mode);
_historyManager.setMode(mode);
loadTranslationHistory();
//stopAll();
// 检查悬浮窗支持情况
@ -397,7 +399,11 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
targetLanguageCode.value = targetAsrCode;
}
// 加载历史记录
// 初始化翻译模式(用于历史隔离的初始加载)
final initMode = Get.arguments?['translationId'] ?? 'simultaneous';
_historyManager.setMode(initMode);
// 按初始模式加载历史
loadTranslationHistory();
// 初始化服务
@ -406,9 +412,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// 初始化历史管理器
_historyManager.initialize();
// 初始化翻译模式,根据跳转参数
final mode = Get.arguments?['translationId'] ?? 'simultaneous';
changeTranslationMode(mode);
// 应用模式逻辑(含ASR/TTS配置等)
changeTranslationMode(initMode);
// 监听应用生命周期:当应用切到后台/失焦时兜底重置 activeSpeaker
WidgetsBinding.instance.addObserver(this);
@ -847,53 +852,64 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
/// 处理语音识别事件
/// [event] 识别事件
void _handleRecognitionEvent(RecognitionEvent event) {
if (event.type == RecognitionEventType.finalResult &&
event.text.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage;
print("处理识别完成:${event.text}");
if (currentMode.value != 'faceToFace') {
handleFinalResult(event.text);
} else {
faceToFaceRecognitionText.value += event.text;
// 添加日志打印
Logger.info('lxm-面对面翻译识别文本更新: ${faceToFaceRecognitionText.value}');
}
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
handleFinalResult(faceToFaceRecognitionText.value);
faceToFaceRecognitionText.value = '';
}
} else if (event.type == RecognitionEventType.finalResult1 &&
event.text.isNotEmpty) {
// 通话模式下:己方(麦克风)识别完成事件
// 若原生未返回检测语言,则兜底为源语言
detectedLanguageCode = event.detectedLanguage.isNotEmpty
? event.detectedLanguage
: sourceLanguageCode.value;
print("处理己方识别完成:${event.text}");
if (currentMode.value != 'faceToFace') {
// Logger.info("处理识别事件:${event}");
switch (event.type) {
case RecognitionEventType.finalResult:
if (event.text.isEmpty) break;
// 通话模式下:目标语言即为识别语言,无需判断
detectedLanguageCode = currentMode.value == 'call'
? targetLanguageCode.value
: event.detectedLanguage;
Logger.info("处理识别完成:${event.text}");
if (currentMode.value != 'faceToFace') {
handleFinalResult(event.text);
} else {
faceToFaceRecognitionText.value += event.text;
Logger.info('lxm-面对面翻译识别文本更新: ${faceToFaceRecognitionText.value}');
}
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
handleFinalResult(faceToFaceRecognitionText.value);
faceToFaceRecognitionText.value = '';
}
break;
case RecognitionEventType.finalResult1:
// 通话模式下:己方(麦克风)识别完成事件(对应ast服务)
if (event.text.isEmpty) break;
//通话模式下:己方语言即为源语言,无需判断
detectedLanguageCode = sourceLanguageCode.value;
Logger.info("处理己方识别完成:${event.text}");
handleFinalResult(event.text);
} else {
faceToFaceRecognitionText.value += event.text;
Logger.info('lxm-面对面翻译识别文本更新(己方): ${faceToFaceRecognitionText.value}');
}
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
handleFinalResult(faceToFaceRecognitionText.value);
faceToFaceRecognitionText.value = '';
}
} else if (event.type == RecognitionEventType.intermediateResult &&
event.text.isNotEmpty) {
if (event.detectedLanguage.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage;
}
print("处理识别中事件:${event.text}");
handleIntermediateResult(event.text);
} else if (event.type == RecognitionEventType.error) {
Get.snackbar('error'.tr, event.error);
isRecognizing.value = false;
Logger.error('语音识别错误: ${event.error}');
} else if (event.type == RecognitionEventType.canceled) {
isRecognizing.value = false;
Logger.error('语音识别取消: ${event.error}');
break;
case RecognitionEventType.intermediateResult:
if (event.text.isEmpty) break;
// 通话模式下:目标语言即为识别语言,无需判断
detectedLanguageCode = currentMode.value == 'call'
? targetLanguageCode.value
: event.detectedLanguage;
Logger.info("处理识别中事件:${event.text}");
handleIntermediateResult(event.text);
break;
case RecognitionEventType.error:
Get.snackbar('error'.tr, event.error);
isRecognizing.value = false;
Logger.error('语音识别错误: ${event.error}');
break;
case RecognitionEventType.canceled:
isRecognizing.value = false;
Logger.error('语音识别取消: ${event.error}');
break;
case RecognitionEventType.sessionStarted:
// 可按需处理会话开始事件
break;
case RecognitionEventType.sessionStopped:
// 可按需处理会话结束事件
break;
}
}
@ -919,18 +935,21 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
/// [text] 识别的文本
void handleIntermediateResult(String text) {
if (text.isEmpty) return;
print(
Logger.info(
"handleIntermediateResult:${translationHistory.isEmpty}${translationHistory.isNotEmpty ? translationHistory.last.isIntermediate : 'N/A'}");
if (translationHistory.isEmpty ||
(translationHistory.isNotEmpty &&
!translationHistory.last.isIntermediate)) {
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString();
// 关键调整:使用与最终结果一致的语言方向,避免左右“跳变”
final languageCodes = _determineLanguageCodes();
final newItem = TranslationItem(
sourceText: text,
translatedText: '',
sourceLanguageCode: sourceLanguageCode.value,
targetLanguageCode: targetLanguageCode.value,
sourceLanguageCode: languageCodes['source']!,
targetLanguageCode: languageCodes['target']!,
timestamp: DateTime.now(),
sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty ||

34
lib/modules/translation/controllers/translation_history_controller.dart

@ -15,6 +15,12 @@ class TranslationHistoryManager extends GetxController {
static const int _maxStorageItems = 1000; // 存储限制
static const int _maxDisplayItems = 20; // 显示限制
String _currentMode = 'simultaneous'; // 当前模式
String _getHistoryKey() => '${_historyKey}_$_currentMode'; // 模式化存储键
void setMode(String mode) {
_currentMode = mode;
}
final GetStorage _storage = GetStorage();
// 翻译历史数据
@ -40,7 +46,7 @@ class TranslationHistoryManager extends GetxController {
/// 加载翻译历史记录
/// 初始化时只加载最近的显示条数,提高性能
void loadTranslationHistory() {
final historyJson = _storage.read(_historyKey);
final historyJson = _storage.read(_getHistoryKey());
if (historyJson != null) {
try {
final List<dynamic> historyList = jsonDecode(historyJson);
@ -64,15 +70,15 @@ class TranslationHistoryManager extends GetxController {
Logger.info('翻译历史加载成功: ${translationHistory.length}条记录');
} catch (e) {
_storage.remove(_historyKey);
_storage.remove(_getHistoryKey());
Logger.error('加载翻译历史失败: ${e.toString()}');
}
} else {
// 没有历史数据时初始化分页状态
hasMoreHistory.value = false;
currentPage.value = 1;
isLoadingMore.value = false;
isLoadingMoreError.value = false;
translationHistory.clear();
}
}
@ -94,7 +100,7 @@ class TranslationHistoryManager extends GetxController {
// 保存到本地存储
final List<Map<String, dynamic>> historyJson =
historyToSave.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(historyJson));
_storage.write(_getHistoryKey(), jsonEncode(historyJson));
Logger.info('翻译历史保存成功: 存储${historyToSave.length}条记录');
} catch (e) {
@ -141,7 +147,7 @@ class TranslationHistoryManager extends GetxController {
Logger.i(TAG, '正在加载第 $nextPage 页翻译历史...');
// 从存储中获取所有历史记录
final historyJson = _storage.read(_historyKey);
final historyJson = _storage.read(_getHistoryKey());
if (historyJson != null) {
final List<dynamic> historyList = jsonDecode(historyJson);
final List<TranslationItem> allHistory =
@ -194,8 +200,8 @@ class TranslationHistoryManager extends GetxController {
final List<TranslationItem> result = <TranslationItem>[];
final Set<String> existingIds = <String>{};
// 添加存储的历史记录
final historyJson = _storage.read(_historyKey);
// 添加存储的历史记录(按当前模式)
final historyJson = _storage.read(_getHistoryKey());
if (historyJson != null) {
final List<dynamic> historyList = jsonDecode(historyJson);
final List<TranslationItem> storedHistory =
@ -235,7 +241,7 @@ class TranslationHistoryManager extends GetxController {
/// 清除所有翻译历史
void clearTranslationHistory() {
translationHistory.clear();
_storage.remove(_historyKey);
_storage.remove(_getHistoryKey());
Logger.info('翻译历史已清除');
}
@ -247,8 +253,8 @@ class TranslationHistoryManager extends GetxController {
translationHistory.removeWhere((item) => item.sessionId == sessionId);
final removedFromMemory = originalCount - translationHistory.length;
// 从存储中删除
final historyJson = _storage.read(_historyKey);
// 从存储中删除(按当前模式)
final historyJson = _storage.read(_getHistoryKey());
int removedFromStorage = 0;
if (historyJson != null) {
@ -263,7 +269,7 @@ class TranslationHistoryManager extends GetxController {
// 重新保存
final List<Map<String, dynamic>> updatedHistoryJson =
storedHistory.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(updatedHistoryJson));
_storage.write(_getHistoryKey(), jsonEncode(updatedHistoryJson));
}
translationHistory.refresh();
@ -287,8 +293,8 @@ class TranslationHistoryManager extends GetxController {
timestamp.millisecondsSinceEpoch);
final removedFromMemory = originalCount - translationHistory.length;
// 从存储中删除
final historyJson = _storage.read(_historyKey);
// 从存储中删除(按当前模式)
final historyJson = _storage.read(_getHistoryKey());
int removedFromStorage = 0;
if (historyJson != null) {
@ -306,7 +312,7 @@ class TranslationHistoryManager extends GetxController {
// 重新保存
final List<Map<String, dynamic>> updatedHistoryJson =
storedHistory.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(updatedHistoryJson));
_storage.write(_getHistoryKey(), jsonEncode(updatedHistoryJson));
}
translationHistory.refresh();

25
lib/modules/translation/views/translation_view.dart

@ -80,6 +80,31 @@ class TranslationView extends GetView<TranslationController> {
controller: controller,
),
Positioned(
top: 2,
left: 0,
right: 0,
child: SafeArea(
top: true,
child: Container(
height: 56.h,
padding: EdgeInsets.symmetric(horizontal: 8.w),
color: Colors.transparent,
child: Align(
alignment: Alignment.centerLeft,
child: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
),
),
),
),
// 顶部按钮(倒过来)
Positioned(
top: 50.h,

130
lib/modules/translation/views/widgets/face_to_face_layout.dart

@ -7,7 +7,7 @@ import '../../../../core/theme/app_colors.dart';
import '../dialogs/language_selection_dialog.dart';
/// 面对面翻译布局组件
class FaceToFaceLayout extends StatelessWidget {
class FaceToFaceLayout extends StatefulWidget {
final bool isDarkMode;
final TranslationController controller;
@ -17,13 +17,60 @@ class FaceToFaceLayout extends StatelessWidget {
required this.controller,
}) : super(key: key);
@override
State<FaceToFaceLayout> createState() => _FaceToFaceLayoutState();
}
class _FaceToFaceLayoutState extends State<FaceToFaceLayout> {
final ScrollController _topScrollController = ScrollController();
final ScrollController _bottomScrollController = ScrollController();
@override
void dispose() {
_topScrollController.dispose();
_bottomScrollController.dispose();
super.dispose();
}
void _scrollToBottom({bool animate = false}) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_topScrollController.hasClients) {
final m = _topScrollController.position.maxScrollExtent;
if (animate) {
_topScrollController.animateTo(
m,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
} else {
_topScrollController.jumpTo(m);
}
}
if (_bottomScrollController.hasClients) {
final m2 = _bottomScrollController.position.maxScrollExtent;
if (animate) {
_bottomScrollController.animateTo(
m2,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
} else {
_bottomScrollController.jumpTo(m2);
}
}
});
}
@override
Widget build(BuildContext context) {
final isDarkMode = widget.isDarkMode;
final controller = widget.controller;
return Obx(() {
// 获取最新的翻译记录
final latestItem = controller.translationHistory.isNotEmpty
? controller.translationHistory.last
: null;
_scrollToBottom(animate: false);
return Column(
children: [
@ -33,7 +80,7 @@ class FaceToFaceLayout extends StatelessWidget {
angle: 3.14159, // 180度 = π弧度
child: Container(
width: double.infinity,
padding: EdgeInsets.all(24.w),
padding: EdgeInsets.fromLTRB(24.w, 24.w, 24.w, 24.w + 96.h),
decoration: BoxDecoration(
color: isDarkMode
? AppColors.cardBackground.withValues(alpha: 0.5)
@ -45,24 +92,22 @@ class FaceToFaceLayout extends StatelessWidget {
),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 上半部的文本
Text(
latestItem?.targetLanguageCode ==
controller.targetLanguageCode.value
? latestItem?.translatedText ?? '' //如果为null则显示空字符
: latestItem?.sourceText ?? '',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
height: 1.4,
),
textAlign: TextAlign.center,
child: SingleChildScrollView(
controller: _topScrollController,
physics: const BouncingScrollPhysics(),
child: Text(
latestItem?.targetLanguageCode ==
controller.targetLanguageCode.value
? latestItem?.translatedText ?? ''
: latestItem?.sourceText ?? '',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
height: 1.4,
),
],
textAlign: TextAlign.center,
),
),
),
),
@ -75,33 +120,31 @@ class FaceToFaceLayout extends StatelessWidget {
Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(24.w),
padding: EdgeInsets.fromLTRB(24.w, 24.w, 24.w, 24.w + 96.h),
decoration: BoxDecoration(
color: isDarkMode
? AppColors.cardBackground.withValues(alpha: 0.5)
: Colors.green[50]?.withValues(alpha: 0.3),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 下半部的文本
Text(
latestItem?.sourceLanguageCode ==
controller.sourceLanguageCode.value
? latestItem?.sourceText ??
(controller.faceToFaceRecognitionText.value.isEmpty
? ''
: controller.faceToFaceRecognitionText.value)
: latestItem?.translatedText ?? '',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
height: 1.4,
),
textAlign: TextAlign.center,
child: SingleChildScrollView(
controller: _bottomScrollController,
physics: const BouncingScrollPhysics(),
child: Text(
latestItem?.sourceLanguageCode ==
controller.sourceLanguageCode.value
? latestItem?.sourceText ??
(controller.faceToFaceRecognitionText.value.isEmpty
? ''
: controller.faceToFaceRecognitionText.value)
: latestItem?.translatedText ?? '',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
height: 1.4,
),
],
textAlign: TextAlign.center,
),
),
),
),
@ -146,7 +189,7 @@ class FaceToFaceLayout extends StatelessWidget {
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
'${controller.sourceLanguage.value.split(' ')[0]}'.tr,
'${widget.controller.sourceLanguage.value.split(' ')[0]}'.tr,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
@ -161,7 +204,7 @@ class FaceToFaceLayout extends StatelessWidget {
// 语言互换按钮
GestureDetector(
onTap: controller.swapLanguages,
onTap: widget.controller.swapLanguages,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.w),
padding: EdgeInsets.all(4.r),
@ -194,7 +237,8 @@ class FaceToFaceLayout extends StatelessWidget {
child: Transform.rotate(
angle: 3.14159, // 旋转180度
child: Text(
'${controller.targetLanguage.value.split(' ')[0]}'.tr,
'${widget.controller.targetLanguage.value.split(' ')[0]}'
.tr,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,

10
lib/modules/translation/views/widgets/translation_item_widget.dart

@ -49,15 +49,19 @@ class TranslationItemWidget extends GetView<TranslationController> {
// 判断是否是己方的翻译项(依据控制器当前源语言)
final bool isSelf =
item.sourceLanguageCode == controller.sourceLanguageCode.value;
// 新增:通话模式标记,用于控制右侧显示与隐藏中间态
final bool isCallMode = controller.currentMode.value == 'call';
return GestureDetector(
onTap: () => onToggleActionItem(item), // 触碰气泡时切换按钮显隐
onLongPressStart: (details) =>
onLongPressStart(context, isDarkMode, details, item),
child: Align(
alignment: item.isIntermediate
? Alignment.center
: (isSelf ? Alignment.centerLeft : Alignment.centerRight),
alignment: isCallMode
? (isSelf ? Alignment.centerLeft : Alignment.centerRight)
: item.isIntermediate
? Alignment.center
: (isSelf ? Alignment.centerLeft : Alignment.centerRight),
child: Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),

10
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt

@ -219,11 +219,11 @@ class BleCommandSender {
// 正常响应处理
val length = data[2].toInt() and 0xFF
// 检查数据长度
if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC
Log.e(TAG, "设备响应数据长度不匹配: 期望=${length + 4}, 实际=${data.size}")
return
}
// // 检查数据长度
// if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC
// Log.e(TAG, "设备响应数据长度不匹配: 期望=${length + 4}, 实际=${data.size}")
// return
// }
// 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节
val payload = data.sliceArray(0 until data.size - 1)

2
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -893,12 +893,14 @@ object BleService {
// 统计接收到的字节数
bytesReceivedInCurrentSecond += data.size
processAudioData(data)
Log.i(TAG, "lxm--收到音频特征数据, 字节数=${data.size}")
}
BleConst.CALL_RECEIVE_AUDIO_CHAR_UUID -> {
// 统计接收到的字节数
bytesReceivedInCurrentSecond += data.size
processAudioData(data)
Log.i(TAG, "lxm--收到通话音频特征数据, 字节数=${data.size}")
}
// 通知特征数据(命令和控制)
BleConst.NOTIFY_CHAR_UUID -> {

51
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift

@ -282,22 +282,11 @@ private var cmdReplyType: UInt8 = 0
// 处理传入的UUID参数
for uuidStr in uuids {
do {
let cbuuid: CBUUID
if uuidStr.count == 4 {
// 短UUID,直接使用
cbuuid = CBUUID(string: uuidStr)
} else if uuidStr.count == 36 {
// 完整UUID
cbuuid = CBUUID(string: uuidStr)
} else {
os_log("⚠️ 无效的UUID格式: %{public}@", log: logger, type: .default, uuidStr)
continue
}
if let cbuuid = parseCBUUID(from: uuidStr) {
serviceUUIDs.append(cbuuid)
os_log("✅ 添加扫描过滤器UUID: %{public}@", log: logger, type: .info, uuidStr)
} catch {
os_log("❌ 构造UUID失败: %{public}@, 错误: %{public}@", log: logger, type: .error, uuidStr, error.localizedDescription)
} else {
os_log("⚠️ 无效的UUID格式或字符: %{public}@", log: logger, type: .error, uuidStr)
}
}
@ -319,6 +308,40 @@ private var cmdReplyType: UInt8 = 0
}
return true
}
/// 验证并解析 UUID 字符串为 CBUUID
/// - Parameter uuidStr: UUID 字符串(支持 4 位短 UUID、36 位完整 UUID;扩展支持 32 位不带短横线 UUID)
/// - Returns: 解析成功返回 CBUUID,否则返回 nil
private func parseCBUUID(from uuidStr: String) -> CBUUID? {
let trimmed = uuidStr.trimmingCharacters(in: .whitespacesAndNewlines)
// 4 位短 UUID:仅允许十六进制字符,通过正则校验
if trimmed.count == 4 {
if trimmed.range(of: "^[0-9a-fA-F]{4}$", options: .regularExpression) != nil {
return CBUUID(string: trimmed.lowercased())
} else {
return nil
}
}
// 36 位完整 UUID:使用系统 UUID 进行格式校验
if trimmed.count == 36, let nsuuid = UUID(uuidString: trimmed) {
return CBUUID(nsuuid: nsuuid)
}
// 32 位完整 UUID(不带短横线):按 8-4-4-4-12 格式插入短横线后校验
if trimmed.count == 32,
trimmed.range(of: "^[0-9a-fA-F]{32}$", options: .regularExpression) != nil {
let lower = trimmed.lowercased()
let formatted = "\(lower.prefix(8))-\(lower.dropFirst(8).prefix(4))-\(lower.dropFirst(12).prefix(4))-\(lower.dropFirst(16).prefix(4))-\(lower.dropFirst(20))"
if let nsuuid = UUID(uuidString: formatted) {
return CBUUID(nsuuid: nsuuid)
}
}
// 其他长度不支持
return nil
}
/// 停止扫描
public func stopScan() {

Loading…
Cancel
Save