From d0b00c40ece22074b77e5627f589dcd736f25d62 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 13 Nov 2025 11:37:03 +0800 Subject: [PATCH 1/9] =?UTF-8?q?refactor(BleService):=20=E6=8F=90=E5=8F=96U?= =?UTF-8?q?UID=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91=E5=88=B0=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E6=96=B9=E6=B3=95=E5=B9=B6=E5=A2=9E=E5=BC=BA=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=EF=BC=88=E8=A7=A3=E5=86=B311=E6=9C=8812=E6=97=A5?= =?UTF-8?q?=E5=B4=A9=E6=BA=83=E9=97=AE=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将UUID字符串解析逻辑提取到独立的parseCBUUID方法,增加对32位不带短横线UUID的支持,并优化输入校验。移除冗余的try-catch结构,简化主扫描逻辑。 --- .../Sources/ble_service/BleService.swift | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift index a6914b556..84c3a4a44 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift +++ b/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() { From 961f85c50b7654b358bf5f721043486d94ebaa42 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 13 Nov 2025 16:40:04 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(translation):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=80=9A=E8=AF=9D=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9A=84=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E9=A1=B9=E5=B8=83=E5=B1=80=E5=92=8C=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整翻译项在通话模式下的对齐方式,新增通话模式标记控制显示 重构语音识别事件处理逻辑,优化语言代码确定方式 添加BLE服务音频数据接收日志,便于调试 --- .../controllers/translation_controller.dart | 112 ++++++++++-------- .../widgets/translation_item_widget.dart | 10 +- .../yunqiinnovation/ble_service/BleService.kt | 2 + 3 files changed, 72 insertions(+), 52 deletions(-) diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 037ea6aa2..30aef106f 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -847,53 +847,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 +930,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 || diff --git a/lib/modules/translation/views/widgets/translation_item_widget.dart b/lib/modules/translation/views/widgets/translation_item_widget.dart index 49ce94472..56ae1b264 100644 --- a/lib/modules/translation/views/widgets/translation_item_widget.dart +++ b/lib/modules/translation/views/widgets/translation_item_widget.dart @@ -49,15 +49,19 @@ class TranslationItemWidget extends GetView { // 判断是否是己方的翻译项(依据控制器当前源语言) 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), diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 71dea5336..02f2c6cc6 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/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 -> { From 941c1d7c437700b0e2ddb8a64461f6f4da62b111 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 13 Nov 2025 17:24:32 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat(translation):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=8C=89=E7=BF=BB=E8=AF=91=E6=A8=A1=E5=BC=8F=E9=9A=94=E7=A6=BB?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加模式隔离机制,使不同翻译模式的历史记录独立存储和加载 初始化时直接设置模式并加载对应历史,优化切换模式时的历史管理 --- .../controllers/translation_controller.dart | 13 ++++--- .../translation_history_controller.dart | 34 +++++++++++-------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 30aef106f..79cac6ab1 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -234,6 +234,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver { Future 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); diff --git a/lib/modules/translation/controllers/translation_history_controller.dart b/lib/modules/translation/controllers/translation_history_controller.dart index b7e7d15b3..f94f1f7a6 100644 --- a/lib/modules/translation/controllers/translation_history_controller.dart +++ b/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 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> 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 historyList = jsonDecode(historyJson); final List allHistory = @@ -194,8 +200,8 @@ class TranslationHistoryManager extends GetxController { final List result = []; final Set existingIds = {}; - // 添加存储的历史记录 - final historyJson = _storage.read(_historyKey); + // 添加存储的历史记录(按当前模式) + final historyJson = _storage.read(_getHistoryKey()); if (historyJson != null) { final List historyList = jsonDecode(historyJson); final List 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> 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> updatedHistoryJson = storedHistory.map((item) => item.toJson()).toList(); - _storage.write(_historyKey, jsonEncode(updatedHistoryJson)); + _storage.write(_getHistoryKey(), jsonEncode(updatedHistoryJson)); } translationHistory.refresh(); From fdd7c098a53478468af747a3a243ef63b6260a38 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 13 Nov 2025 17:59:44 +0800 Subject: [PATCH 4/9] =?UTF-8?q?refactor(translation):=20=E5=B0=86=E9=9D=A2?= =?UTF-8?q?=E5=AF=B9=E9=9D=A2=E5=B8=83=E5=B1=80=E7=BB=84=E4=BB=B6=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=9C=89=E7=8A=B6=E6=80=81=E7=BB=84=E4=BB=B6=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=BB=9A=E5=8A=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将StatelessWidget改为StatefulWidget以管理滚动控制器 添加自动滚动到底部功能,提升长文本浏览体验 优化布局内边距,防止底部内容被遮挡 --- .../views/widgets/face_to_face_layout.dart | 130 ++++++++++++------ 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/lib/modules/translation/views/widgets/face_to_face_layout.dart b/lib/modules/translation/views/widgets/face_to_face_layout.dart index 562a543b9..efc6140a3 100644 --- a/lib/modules/translation/views/widgets/face_to_face_layout.dart +++ b/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 createState() => _FaceToFaceLayoutState(); +} + +class _FaceToFaceLayoutState extends State { + 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, From c612065ad1e71876734c88221c00d35133be52a0 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 13 Nov 2025 19:41:04 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor(pairing):=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E6=8E=89=E5=88=9D=E5=A7=8B=E6=A3=80=E6=9F=A5=E7=9A=84=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 直接调用状态检查而不需要中间状态转换,简化流程 --- lib/modules/pairing/controllers/pairing_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 44200e5fb..19d7714d2 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/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(); } From 8aca1f0a2acdb9dd41315543e6a8d68d567359ea Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Fri, 14 Nov 2025 17:00:38 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E9=99=90=E5=88=B6ota=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/ota_upgrade/views/ota_upgrade_view.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart index 18dc7485f..a1663da02 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart @@ -13,7 +13,7 @@ class OtaUpgradeView extends GetView { 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 { 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 { // 更新按钮 Widget _buildUpdateButton(bool isDarkMode) { return Obx(() { - if (controller.isUpgrade.value) { + if (controller.isUpgrade.value && controller.showDetailedStatus.value) { return _buildProgressIndicator(isDarkMode); } From 6b8e2151e674c13812f343db6af23590f2c1eaaf Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Fri, 14 Nov 2025 17:32:31 +0800 Subject: [PATCH 7/9] 1 --- .../yunqiinnovation/ble_service/BleCommandSender.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt index 44bb4f630..692f83ada 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt +++ b/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) From ae068abd04b3b5461e9c0ffc52422f242be7095b Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 14 Nov 2025 21:17:53 +0800 Subject: [PATCH 8/9] =?UTF-8?q?feat(ota):=20=E6=B7=BB=E5=8A=A0=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=89=88=E6=9C=AC=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在DBProduct模型中添加isforceupdate字段标记强制更新 - 修改版本检查逻辑仅支持升级不支持降级 - 当检测到强制更新版本时不标记为已检查 - 缩短首页引导后的OTA检查延迟时间 --- lib/data/models/appconfig_model.dart | 2 ++ lib/data/models/appconfig_model.g.dart | 2 ++ lib/data/services/ota_check_service.dart | 18 +++++++++++++++--- .../home/controllers/home_controller.dart | 2 +- .../controllers/ota_core_contropller.dart | 13 +++++++++---- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/data/models/appconfig_model.dart b/lib/data/models/appconfig_model.dart index 65ea7838e..e656a62c9 100644 --- a/lib/data/models/appconfig_model.dart +++ b/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, diff --git a/lib/data/models/appconfig_model.g.dart b/lib/data/models/appconfig_model.g.dart index f99ba35c6..fc97723ed 100644 --- a/lib/data/models/appconfig_model.g.dart +++ b/lib/data/models/appconfig_model.g.dart @@ -79,6 +79,7 @@ DBProduct _$DBProductFromJson(Map 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 _$DBProductToJson(DBProduct instance) => { 'productimage': instance.productimage, 'scanuuid': instance.scanuuid, 'version': instance.version, + 'isforceupdate': instance.isforceupdate, 'iscontinuouschat': instance.iscontinuouschat, 'devicename': instance.devicename, 'updatedescription': instance.updatedescription, diff --git a/lib/data/services/ota_check_service.dart b/lib/data/services/ota_check_service.dart index 5caa31493..13a5f09d5 100644 --- a/lib/data/services/ota_check_service.dart +++ b/lib/data/services/ota_check_service.dart @@ -57,6 +57,7 @@ class OtaCheckService extends GetxService { /// [isFullyConnected] 设备是否完全连接 /// 返回是否成功执行了检查 Future 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; } } diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 466b4792b..ab2478230 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/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(); }); } diff --git a/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart b/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart index cd05687f0..146645d90 100644 --- a/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart +++ b/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 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; } } From c87d878e154ebaa3362200e29de3fabdb205c335 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 14 Nov 2025 21:40:18 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat(translation=5Fview):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=BF=94=E5=9B=9E=E6=8C=89=E9=92=AE=E5=88=B0=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E8=A7=86=E5=9B=BE=E9=A1=B6=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在翻译视图顶部添加返回按钮,方便用户快速返回上一页。按钮会根据当前主题模式自动调整颜色。 --- .../translation/views/translation_view.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index b51cf7fa0..25362ae6e 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -80,6 +80,31 @@ class TranslationView extends GetView { 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,