From dbb81cd8f7a73cb2cbae15cef15a1533b7ad9a08 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 19:51:49 +0800 Subject: [PATCH 1/6] =?UTF-8?q?refactor(splash):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=B5=81=E7=A8=8B=E4=B8=BA=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=B9=B6=E4=BC=98=E5=8C=96=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将资源加载状态拆分为多个独立任务状态,包括追踪权限、版本检查、Token验证和应用配置 优化启动流程为并行执行,在1.5秒动画期间完成所有任务 添加任务完成状态检查确保所有任务完成后才进行页面跳转 --- .../splash/controllers/splash_controller.dart | 156 ++++++++++-------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 4bf70d207..013ba187e 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -24,9 +24,16 @@ class SplashController extends GetxController { final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; - // 资源加载状态 - final _isResourceLoaded = false.obs; - bool get isResourceLoaded => _isResourceLoaded.value; + // 各项任务完成状态 + final _isTrackingPermissionRequested = false.obs; // 追踪权限请求完成 + final _isVersionCheckCompleted = false.obs; // 版本检查完成 + final _isTokenValidated = false.obs; // Token验证完成 + final _isAppConfigLoaded = false.obs; // 应用配置加载完成 + final _isAnimationCompleted = false.obs; // 动画完成 + + // 登录状态 + final _isLoggedIn = false.obs; + bool get isLoggedIn => _isLoggedIn.value; final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策 @@ -59,38 +66,45 @@ class SplashController extends GetxController { } }); - // 异步处理用户追踪授权,不阻塞主流程 - _requestTrackingPermissionAsync(); + // 注意:追踪权限请求将在用户同意隐私政策后执行 } - /// 异步请求追踪权限,不阻塞启动流程 - void _requestTrackingPermissionAsync() { - Future.delayed(const Duration(milliseconds: 500), () async { - try { - await AppTrackingTransparency.requestTrackingAuthorization(); - Logger.d(_tag, '用户追踪权限请求完成'); - } catch (e) { - Logger.e(_tag, '用户追踪权限请求失败: $e'); - } - }); + /// 异步请求追踪权限 + Future _requestTrackingPermissionAsync() async { + try { + await AppTrackingTransparency.requestTrackingAuthorization(); + Logger.d(_tag, '用户追踪权限请求完成'); + } catch (e) { + Logger.e(_tag, '用户追踪权限请求失败: $e'); + } finally { + _isTrackingPermissionRequested.value = true; + } } void _startNormalFlow() { _startLoadingAnimation(); - _loadAppResources(); + _startParallelTasks(); } - /// 智能调度版本检查,在页面跳转后执行,完全不影响启动流程 - void _scheduleVersionCheck() { - // 在独立的微任务中执行,避免阻塞主线程 - scheduleMicrotask(() async { - try { - Logger.d(_tag, '开始执行版本检查(后台任务)'); - _checkVersionUpdateIfNeeded(); - } catch (e) { - Logger.e(_tag, '版本检查调度失败: $e'); - } - }); + /// 启动并行任务:在1.5秒动画期间同时执行多个任务 + void _startParallelTasks() { + // 并行执行所有任务 + _requestTrackingPermissionAsync(); + _performVersionCheckAsync(); + _validateTokenAsync(); + } + + /// 异步执行版本检查 + Future _performVersionCheckAsync() async { + try { + Logger.d(_tag, '开始执行版本检查'); + await _checkVersionUpdateIfNeeded(); + } catch (e) { + Logger.e(_tag, '版本检查失败: $e'); + } + //finally { + // _isVersionCheckCompleted.value = true; + // } } void _onPrivacyAccepted() { @@ -115,10 +129,10 @@ class SplashController extends GetxController { ); } - // 进度条动画(持续2秒) + // 进度条动画(持续1.5秒) void _startLoadingAnimation() { - const totalDuration = 1500; // 动画总时长增加到1.5秒 - const steps = totalDuration ~/ 30; // 30的间隔,更平滑的动画 + const totalDuration = 1500; // 动画总时长1.5秒 + const steps = totalDuration ~/ 30; // 30ms间隔,更平滑的动画 const stepValue = 1.0 / steps; Timer.periodic(const Duration(milliseconds: 30), (timer) { @@ -126,54 +140,53 @@ class SplashController extends GetxController { _loadingProgress.value += stepValue; } else { timer.cancel(); + _isAnimationCompleted.value = true; _checkNavigateCondition(); } }); } - // 异步加载应用资源 - Future _loadAppResources() async { + /// 异步验证Token和加载应用配置 + Future _validateTokenAsync() async { try { - // 模拟资源加载(如API配置、本地缓存等) - await Future.delayed(const Duration(milliseconds: 1500)); - - // 实际资源加载代码 - // await _loadImages(); - // await _initDatabase(); + bool isLoggedIn = false; + final token = _storage.read('logintoken'); + Logger.d(_tag, 'Token验证开始: $token'); + + if (token != null && token.isNotEmpty) { + isLoggedIn = await getLoginToken(); + if (isLoggedIn) { + await getAppConfig(); + _isAppConfigLoaded.value = true; + } + } - _isResourceLoaded.value = true; - _checkNavigateCondition(); + _isLoggedIn.value = isLoggedIn; + Logger.d(_tag, 'Token验证完成,登录状态: $isLoggedIn'); } catch (e) { - Get.snackbar('错误', '资源加载失败,请检查网络连接'); - _isResourceLoaded.value = true; // 允许用户继续使用 - Logger.e(_tag, '资源加载错误: $e'); + Logger.e(_tag, 'Token验证失败: $e'); + _isLoggedIn.value = false; + } finally { + _isTokenValidated.value = true; } } - // 检查跳转条件 - void _checkNavigateCondition() async { - if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) { - // 关键改动:先执行页面跳转,并用 await 等待它完成 - await _navigateToLogin(); - - // 页面跳转完全结束后,再安心地安排版本检查 - _scheduleVersionCheck(); + // 检查跳转条件:所有任务都完成后才跳转 + void _checkNavigateCondition() { + if (_isAnimationCompleted.value && + _isTrackingPermissionRequested.value && + _isVersionCheckCompleted.value && + _isTokenValidated.value) { + _navigateToTargetPage(); } } - // 获取登录Token并跳转 - Future _navigateToLogin() async { - bool isLoggedIn = false; - final token = _storage.read('logintoken'); - Logger.d(_tag, 'getLoginToken:进来了${token}'); - if (token != null && token.isNotEmpty) { - isLoggedIn = await getLoginToken(); - } - if (isLoggedIn) { - await getAppConfig(); + // 根据登录状态跳转到目标页面 + void _navigateToTargetPage() { + if (_isLoggedIn.value && _isAppConfigLoaded.value) { Get.offAllNamed(Routes.pairing); } else { - Get.offAllNamed(Routes.login); // 跳转到登录页 + Get.offAllNamed(Routes.login); } } @@ -227,7 +240,7 @@ class SplashController extends GetxController { /// 混合检查机制:判断是否需要进行版本检查 /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时 - void _checkVersionUpdateIfNeeded() { + Future _checkVersionUpdateIfNeeded() async { try { final now = DateTime.now(); final lastCheckTimestamp = _storage.read(_lastVersionCheckKey); @@ -259,7 +272,7 @@ class SplashController extends GetxController { if (shouldCheck) { Logger.i(_tag, '开始版本检查 - 原因: $reason'); - _performVersionCheck(); + await _performVersionCheck(); } else { Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔'); } @@ -278,9 +291,14 @@ class SplashController extends GetxController { Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); // 在主线程中显示更新对话框 WidgetsBinding.instance.addPostFrameCallback((_) { - VersionUpdateDialog.show(updateInfo); + VersionUpdateDialog.show(updateInfo).then((_) { + // 无论用户选择更新还是稍后提醒,都标记版本检查完成 + _isVersionCheckCompleted.value = true; + _checkNavigateCondition(); + }); }); } else { + _isVersionCheckCompleted.value = true; Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}'); } @@ -288,12 +306,16 @@ class SplashController extends GetxController { _updateVersionCheckRecord(); }).catchError((e) { Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也更新记录,避免频繁重试 + // 即使检查失败,也标记版本检查完成,让流程继续 + _isVersionCheckCompleted.value = true; + // 更新检查记录,避免频繁重试 _updateVersionCheckRecord(); }); } catch (e) { Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也更新记录,避免频繁重试 + // 即使检查失败,也标记版本检查完成,让流程继续 + _isVersionCheckCompleted.value = true; + // 更新检查记录,避免频繁重试 _updateVersionCheckRecord(); } } From ab99c62f9868fe410279b48a7aa142e834ff656d Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Fri, 1 Aug 2025 20:00:49 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=AD=E7=A7=8D?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/azure_speech/AzureAsrHelper.swift | 180 ++++++++++++++- .../azure_speech/LanguageDetector.swift | 214 ++++++++++++++++++ 2 files changed, 384 insertions(+), 10 deletions(-) create mode 100644 local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index 97343fa99..5c90a7c67 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -99,22 +99,28 @@ public class AzureAsrHelper: NSObject { // 创建语音配置 speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region) - + // 针对翻译场景的性能优化 + optimizeForTranslation() if isAutoDetectLanguage { - // 移除过时的属性设置方式 - // speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") - speechConfig?.setPropertyTo(supportedLanguages.joined(separator: ","), byName: "SpeechServiceConnection_AutoDetectSourceLanguages") + // 针对翻译场景优化的语言检测配置 + speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") + + // 设置语言检测的置信度阈值,提高准确性 + speechConfig?.setPropertyTo("0.7", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold") // 不在这里设置语言,让SPXAutoDetectSourceLanguageConfiguration处理 os_log("启用语言检测模式,支持语言: %{public}@", log: log, type: .info, supportedLanguages.joined(separator: ",")) } else { speechConfig?.speechRecognitionLanguage = currentLanguage + os_log("固定语言模式,当前语言: %{public}@", log: log, type: .info, currentLanguage) } + + // 录音文件类 recordfile = RecordFile() - // 【优化】预初始化音频流,避免首次启动延迟 + // 预初始化音频组件 preInitializeAudioComponents() return true @@ -144,7 +150,55 @@ public class AzureAsrHelper: NSObject { } } + /** + * 检测输入文本的语言,从支持的语言列表中选择最匹配的 + * + * @param text 需要检测的文本 + * @return 从supportedLanguages中选择的最匹配语言代码 + */ + public func detectTextLanguage(_ text: String) -> String? { + // 如果只有一种支持的语言,直接返回 + if supportedLanguages.count == 1 { + return supportedLanguages[0] + } + + // 获取所有可能的语言及其置信度 + let languageResults = LanguageDetector.detectLanguageWithConfidence(from: text) + + // 遍历检测结果,找到第一个在supportedLanguages中的语言 + for result in languageResults { + let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(result.language) + if supportedLanguages.contains(azureLanguageCode) { + return azureLanguageCode + } + } + + // 如果没有找到匹配的语言,返回第一个支持的语言作为默认值 + return supportedLanguages.first + } + /** + * 检测文本语言并返回详细信息 + * + * @param text 需要检测的文本 + * @return 包含语言代码、置信度和语言名称的字典 + */ + public func detectTextLanguageWithDetails(_ text: String) -> [String: Any]? { + let results = LanguageDetector.detectLanguageWithConfidence(from: text) + guard let topResult = results.first else { + return nil + } + + let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(topResult.language) + let languageName = LanguageDetector.getLanguageName(from: topResult.language) + + return [ + "languageCode": azureLanguageCode, + "confidence": topResult.confidence, + "languageName": languageName, + "originalCode": topResult.language + ] + } /** * 开始连续语音识别 * @@ -156,11 +210,13 @@ public class AzureAsrHelper: NSObject { audioSourceType: AudioSourceType = .microphone ) -> Bool { guard speechConfig != nil else { + print("语音服务未初始化") //callback.onError("语音服务未初始化") return false } if _isContinuousRecognitionActive { + print("连续识别已在进行中") return true } @@ -170,6 +226,7 @@ public class AzureAsrHelper: NSObject { do { guard recognizer != nil else { if !setupRecognizer() { + print("重置识别器失败") // callback.onError("重置识别器失败") return false } @@ -312,7 +369,7 @@ public class AzureAsrHelper: NSObject { */ private func setupRecognizer() -> Bool { do { - print("设置识别器${_isContinuousRecognitionActive}") + print("设置识别器\(_isContinuousRecognitionActive)") // 如果正在进行连续识别,先停止 if _isContinuousRecognitionActive { @@ -332,6 +389,7 @@ public class AzureAsrHelper: NSObject { if isAutoDetectLanguage { // 使用正确的初始化方法 let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) + print("autoDetectConfig: \(autoDetectConfig)") recognizer = try SPXSpeechRecognizer( speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, @@ -425,6 +483,94 @@ public class AzureAsrHelper: NSObject { print("无法检测语言,使用默认语言: \(currentLanguage)") return currentLanguage } + + /** + * 针对翻译场景的性能优化配置 + */ + private func optimizeForTranslation() { + guard let speechConfig = speechConfig else { return } + + // 启用实时转录,减少延迟 + speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableAudioLogging") + speechConfig.setPropertyTo("INTERACTIVE", byName: "SpeechServiceConnection_RecoMode") + speechConfig.setPropertyTo("PCM", byName: "Speech_PushStreamFormat") + // 优化语音端点检测,适合翻译场景 + speechConfig.setPropertyTo("300", byName: "SpeechServiceConnection_InitialSilenceTimeoutMs") + speechConfig.setPropertyTo("500", byName: "SpeechServiceConnection_EndSilenceTimeoutMs") + speechConfig.setPropertyTo("500", byName: "Speech_SegmentationSilenceTimeoutMs") + // 启用标点符号,提高翻译质量 + speechConfig.setPropertyTo("true", byName: "SpeechServiceResponse_RequestProfanityFilterTrueFalse") + // 设置分段策略为时间模式 + speechConfig.setPropertyTo("Time", byName: "Speech_SegmentationStrategy") + + // 【优化】语言检测配置 - 减少检测延迟,实现快速响应 + if isAutoDetectLanguage { + // 降低语言检测置信度阈值,提高检测灵敏度 + speechConfig.setPropertyTo("0.3", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold") + + // 使用Continuous模式,在每句话结束后重新检测语言 + speechConfig.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") + + // 启用快速语言检测 + speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionFast") + + // 【关键优化】设置语言检测的最小音频长度为100毫秒,实现快速响应 + speechConfig.setPropertyTo("100", byName: "SpeechServiceConnection_LanguageDetectionMinAudioLengthMs") + + // 【新增】设置语言检测的最大等待时间,避免长时间等待 + speechConfig.setPropertyTo("2000", byName: "SpeechServiceConnection_LanguageDetectionMaxWaitTimeMs") + + // 强制在每个语音段开始时重新检测语言 + speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_ForceLanguageDetectionOnNewSegment") + + // 【新增】启用句子级别的语言检测 + speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionPerSentence") + + // 【新增】设置语言检测的频率 - 每个识别结果后都检测 + speechConfig.setPropertyTo("PerResult", byName: "SpeechServiceConnection_LanguageDetectionFrequency") + + // 【新增】启用实时语言检测,减少缓冲延迟 + speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableRealtimeLanguageDetection") + + // 【新增】设置语言检测的采样间隔为200毫秒 + speechConfig.setPropertyTo("200", byName: "SpeechServiceConnection_LanguageDetectionSamplingIntervalMs") + } + + os_log("翻译场景优化配置已应用,包含语言检测优化", log: log, type: .info) + } + + /** + * 动态切换语言(翻译场景中的语言切换) + * + * @param newSourceLanguage 新的源语言 + * @param newTargetLanguage 新的目标语言 + * @return 切换是否成功 + */ + public func switchTranslationLanguages( + newSourceLanguage: String, + newTargetLanguage: String + ) -> Bool { + let validatedSourceLang = LanguageDetector.convertToAzureLanguageCode(newSourceLanguage) + let validatedTargetLang = LanguageDetector.convertToAzureLanguageCode(newTargetLanguage) + + // 更新支持的语言 + self.supportedLanguages = [validatedSourceLang, validatedTargetLang] + self.currentLanguage = validatedSourceLang + + // 重新配置语言检测 + self.isAutoDetectLanguage = (validatedSourceLang != validatedTargetLang) + + // 如果正在识别,需要重新设置识别器 + if _isContinuousRecognitionActive { + let wasActive = stopContinuousRecognition() + if wasActive { + return setupRecognizer() + } + } + + os_log("语言已切换: %{public}@ -> %{public}@", log: log, type: .info, validatedSourceLang, validatedTargetLang) + return true + } /** * 设置事件监听器 */ @@ -442,7 +588,14 @@ public class AzureAsrHelper: NSObject { let result = event.result guard !(result.text?.isEmpty ?? true) else { return } - let detectedLanguage = self.getDetectedLanguage(from: result) + // let detectedLanguage = self.getDetectedLanguage(from: result) + + // 检测文本语言,避免重复调用 + let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" + + // 输出检测到的语言信息 + print("检测到的语言: \(detectedLanguage)") + DispatchQueue.main.async { callback.onRecognizing(result.text ?? "", detectedLanguage) } @@ -455,10 +608,17 @@ public class AzureAsrHelper: NSObject { guard result.reason == .recognizedSpeech, !(result.text?.isEmpty ?? true) else { return } - let detectedLanguage = self.getDetectedLanguage(from: result) + // 检测文本语言,避免重复调用 + let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" + + // 输出检测到的语言信息 + print("检测到的语言: \(detectedLanguage)") + + // 在主线程回调结果 DispatchQueue.main.async { callback.onResult(result.text ?? "", detectedLanguage) } + } // 会话开始事件 recognizer.addSessionStartedEventHandler { (sender, event) in @@ -472,8 +632,8 @@ public class AzureAsrHelper: NSObject { guard let self = self else { return } print("会话结束事件:") // 直接在当前线程调用回调 - // callback.onSessionStopped() - // self._isContinuousRecognitionActive = false + callback.onSessionStopped() + self._isContinuousRecognitionActive = false //self.stopAudioProcessing() } diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift new file mode 100644 index 000000000..e22513510 --- /dev/null +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift @@ -0,0 +1,214 @@ +import Foundation +import NaturalLanguage + +/** + * 语言检测工具类 + * 使用iOS自带的自然语言处理框架检测文本语种 + */ +public class LanguageDetector { + + /** + * 检测文本的主要语言 + * + * @param text 需要检测的文本 + * @return 语言代码(如:"zh-Hans", "en", "ja"等),如果检测失败返回nil + */ + public static func detectLanguage(from text: String) -> String? { + // 检查输入文本是否为空 + guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return nil + } + + // 使用NLLanguageRecognizer进行语言检测 + let recognizer = NLLanguageRecognizer() + recognizer.processString(text) + + // 获取最可能的语言 + guard let dominantLanguage = recognizer.dominantLanguage else { + return nil + } + + return dominantLanguage.rawValue + } + + /** + * 检测文本的语言并返回置信度 + * + * @param text 需要检测的文本 + * @return 语言代码和置信度的字典,按置信度降序排列 + */ + public static func detectLanguageWithConfidence(from text: String) -> [(language: String, confidence: Double)] { + // 检查输入文本是否为空 + guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return [] + } + + let recognizer = NLLanguageRecognizer() + recognizer.processString(text) + + // 获取语言假设及其置信度 + let languageHypotheses = recognizer.languageHypotheses(withMaximum: 5) + + // 转换为元组数组并按置信度排序 + return languageHypotheses.map { (language: $0.key.rawValue, confidence: $0.value) } + .sorted { $0.confidence > $1.confidence } + } + + /** + * 将语言代码转换为人类可读的语言名称 + * + * @param languageCode 语言代码(如:"zh-Hans", "en"等) + * @return 语言的本地化名称 + */ + public static func getLanguageName(from languageCode: String) -> String { + let locale = Locale.current + return locale.localizedString(forLanguageCode: languageCode) ?? languageCode + } + + /** + * 检测文本语言并返回Azure语音服务支持的语言代码格式 + * + * @param text 需要检测的文本 + * @return Azure支持的语言代码格式(如:"zh-CN", "en-US"等) + */ + public static func detectLanguageForAzure(from text: String) -> String? { + guard let detectedLanguage = detectLanguage(from: text) else { + return nil + } + + // 将iOS语言代码转换为Azure语音服务支持的格式 + return convertToAzureLanguageCode(detectedLanguage) + } + + /** + * 将iOS语言代码转换为Azure语音服务支持的语言代码 + * + * @param iosLanguageCode iOS检测到的语言代码 + * @return Azure支持的语言代码 + */ + public static func convertToAzureLanguageCode(_ iosLanguageCode: String) -> String { + let languageMapping: [String: String] = [ + // 中文 + "zh-Hans": "zh-CN", // 简体中文 + "zh-Hant": "zh-TW", // 繁体中文 + "zh-HK": "zh-HK", // 香港中文 + + // 英语系 + "en": "en-US", // 英语(美国) + "en-GB": "en-US", // 英语(英国) + "en-AU": "en-US", // 英语(澳大利亚) + "en-CA": "en-US", // 英语(加拿大) + "en-IN": "en-US", // 英语(印度) + "en-IE": "en-US", // 英语(爱尔兰) + "en-NZ": "en-US", // 英语(新西兰) + "en-ZA": "en-US", // 英语(南非) + + // 亚洲语言 + "ja": "ja-JP", // 日语 + "ko": "ko-KR", // 韩语 + "hi": "hi-IN", // 印地语 + "th": "th-TH", // 泰语 + "vi": "vi-VN", // 越南语 + "id": "id-ID", // 印尼语 + "ms": "ms-MY", // 马来语 + "tl": "fil-PH", // 菲律宾语 + "my": "my-MM", // 缅甸语 + "km": "km-KH", // 高棉语(柬埔寨) + "lo": "lo-LA", // 老挝语 + "si": "si-LK", // 僧伽罗语(斯里兰卡) + "ta": "ta-IN", // 泰米尔语 + "te": "te-IN", // 泰卢固语 + "bn": "bn-IN", // 孟加拉语 + "gu": "gu-IN", // 古吉拉特语 + "kn": "kn-IN", // 卡纳达语 + "ml": "ml-IN", // 马拉雅拉姆语 + "mr": "mr-IN", // 马拉地语 + "pa": "pa-IN", // 旁遮普语 + "ur": "ur-PK", // 乌尔都语 + + // 欧洲语言 + "fr": "fr-FR", // 法语(法国) + "fr-CA": "fr-CA", // 法语(加拿大) + "fr-BE": "fr-BE", // 法语(比利时) + "fr-CH": "fr-CH", // 法语(瑞士) + "de": "de-DE", // 德语(德国) + "de-AT": "de-AT", // 德语(奥地利) + "de-CH": "de-CH", // 德语(瑞士) + "es": "es-ES", // 西班牙语(西班牙) + "es-MX": "es-MX", // 西班牙语(墨西哥) + "es-AR": "es-AR", // 西班牙语(阿根廷) + "es-CO": "es-CO", // 西班牙语(哥伦比亚) + "es-CL": "es-CL", // 西班牙语(智利) + "es-PE": "es-PE", // 西班牙语(秘鲁) + "es-VE": "es-VE", // 西班牙语(委内瑞拉) + "it": "it-IT", // 意大利语 + "pt": "pt-BR", // 葡萄牙语(巴西) + "pt-PT": "pt-PT", // 葡萄牙语(葡萄牙) + "ru": "ru-RU", // 俄语 + "nl": "nl-NL", // 荷兰语 + "nl-BE": "nl-BE", // 荷兰语(比利时) + "sv": "sv-SE", // 瑞典语 + "da": "da-DK", // 丹麦语 + "no": "nb-NO", // 挪威语 + "fi": "fi-FI", // 芬兰语 + "is": "is-IS", // 冰岛语 + "pl": "pl-PL", // 波兰语 + "cs": "cs-CZ", // 捷克语 + "sk": "sk-SK", // 斯洛伐克语 + "hu": "hu-HU", // 匈牙利语 + "ro": "ro-RO", // 罗马尼亚语 + "bg": "bg-BG", // 保加利亚语 + "hr": "hr-HR", // 克罗地亚语 + "sr": "sr-RS", // 塞尔维亚语 + "sl": "sl-SI", // 斯洛文尼亚语 + "et": "et-EE", // 爱沙尼亚语 + "lv": "lv-LV", // 拉脱维亚语 + "lt": "lt-LT", // 立陶宛语 + "uk": "uk-UA", // 乌克兰语 + "be": "be-BY", // 白俄罗斯语 + "mk": "mk-MK", // 马其顿语 + "sq": "sq-AL", // 阿尔巴尼亚语 + "mt": "mt-MT", // 马耳他语 + "ga": "ga-IE", // 爱尔兰语 + "cy": "cy-GB", // 威尔士语 + "eu": "eu-ES", // 巴斯克语 + "ca": "ca-ES", // 加泰罗尼亚语 + "gl": "gl-ES", // 加利西亚语 + + // 中东和非洲语言 + "ar": "ar-SA", // 阿拉伯语(沙特阿拉伯) + "ar-EG": "ar-EG", // 阿拉伯语(埃及) + "ar-AE": "ar-AE", // 阿拉伯语(阿联酋) + "ar-BH": "ar-BH", // 阿拉伯语(巴林) + "ar-DZ": "ar-DZ", // 阿拉伯语(阿尔及利亚) + "ar-IQ": "ar-IQ", // 阿拉伯语(伊拉克) + "ar-JO": "ar-JO", // 阿拉伯语(约旦) + "ar-KW": "ar-KW", // 阿拉伯语(科威特) + "ar-LB": "ar-LB", // 阿拉伯语(黎巴嫩) + "ar-LY": "ar-LY", // 阿拉伯语(利比亚) + "ar-MA": "ar-MA", // 阿拉伯语(摩洛哥) + "ar-OM": "ar-OM", // 阿拉伯语(阿曼) + "ar-QA": "ar-QA", // 阿拉伯语(卡塔尔) + "ar-SY": "ar-SY", // 阿拉伯语(叙利亚) + "ar-TN": "ar-TN", // 阿拉伯语(突尼斯) + "ar-YE": "ar-YE", // 阿拉伯语(也门) + "he": "he-IL", // 希伯来语 + "fa": "fa-IR", // 波斯语(伊朗) + "tr": "tr-TR", // 土耳其语 + "az": "az-AZ", // 阿塞拜疆语 + "ka": "ka-GE", // 格鲁吉亚语 + "hy": "hy-AM", // 亚美尼亚语 + "sw": "sw-KE", // 斯瓦希里语 + "am": "am-ET", // 阿姆哈拉语(埃塞俄比亚) + "zu": "zu-ZA", // 祖鲁语(南非) + "af": "af-ZA", // 南非荷兰语 + + // 其他语言 + "el": "el-GR", // 希腊语 + "eo": "eo", // 世界语 + "la": "la", // 拉丁语 + ] + + return languageMapping[iosLanguageCode] ?? iosLanguageCode + } +} \ No newline at end of file From 7951a02bff40b522c24c28aa45377cc537496b38 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 20:05:55 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(settings):=20=E4=BB=85=E5=9C=A8Android?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=98=BE=E7=A4=BA=E9=9A=90=E7=A7=81=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改设置页面,将隐私设置和权限管理选项包裹在平台判断条件中,确保这些功能只在Android平台显示 --- lib/modules/settings/views/settings_view.dart | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 58d527fdb..784c905ed 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -407,8 +407,6 @@ class SettingsView extends GetView { ? Colors.white.withOpacity(0.1) : Colors.grey[200]), - - // 用户反馈 _buildSimpleNavigationSetting( title: 'feedback'.tr, // 用户反馈 @@ -427,29 +425,33 @@ class SettingsView extends GetView { }, isDarkMode: isDarkMode, ), - Divider( - height: 1, - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200]), + if (Platform.isAndroid) ...[ + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), + + // 隐私设置 + _buildSimpleNavigationSetting( + title: 'privacySettings'.tr, // 隐私设置 + subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 + icon: Icons.shield_outlined, + iconBgColor: isDarkMode + ? Colors.purple[900]!.withOpacity(0.3) + : Colors.purple[100]!, + iconColor: isDarkMode + ? Colors.purple[300]! + : Colors.purple[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + AppSettings.openAppSettings(); + }, + isDarkMode: isDarkMode, + ) + ], - // 隐私设置 - _buildSimpleNavigationSetting( - title: 'privacySettings'.tr, // 隐私设置 - subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 - icon: Icons.shield_outlined, - iconBgColor: isDarkMode - ? Colors.purple[900]!.withOpacity(0.3) - : Colors.purple[100]!, - iconColor: - isDarkMode ? Colors.purple[300]! : Colors.purple[600]!, - titleColor: isDarkMode ? Colors.white : null, - subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - AppSettings.openAppSettings(); - }, - isDarkMode: isDarkMode, - ), Divider( height: 1, color: isDarkMode @@ -597,29 +599,32 @@ class SettingsView extends GetView { onTap: () => Get.toNamed(Routes.OTA_UPGRADE), isDarkMode: isDarkMode, ), - Divider( - height: 1, - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200]), + if (Platform.isAndroid) ...[ + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), - // 添加权限管理设置项 - _buildSimpleNavigationSetting( - title: 'permissionManagement'.tr, // 权限管理 - subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置 - icon: Icons.security, - iconBgColor: isDarkMode - ? Colors.orange[900]!.withOpacity(0.3) - : Colors.orange[100]!, - iconColor: - isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, - titleColor: isDarkMode ? Colors.white : null, - subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - Get.toNamed(Routes.permissions); - }, - isDarkMode: isDarkMode, - ), + // 添加权限管理设置项 + _buildSimpleNavigationSetting( + title: 'permissionManagement'.tr, // 权限管理 + subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置 + icon: Icons.security, + iconBgColor: isDarkMode + ? Colors.orange[900]!.withOpacity(0.3) + : Colors.orange[100]!, + iconColor: isDarkMode + ? Colors.orange[300]! + : Colors.orange[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.permissions); + }, + isDarkMode: isDarkMode, + ), + ] ], ), ), From 7aa06f70fce7d6db3314329ffd237181f0e12079 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 1 Aug 2025 20:06:53 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=89=93=E7=94=B5?= =?UTF-8?q?=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/novice_guide_service.dart | 2 +- lib/modules/agent/views/agent_view.dart | 26 ++++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart index 56ab6464b..060bb5c29 100644 --- a/lib/data/services/novice_guide_service.dart +++ b/lib/data/services/novice_guide_service.dart @@ -25,7 +25,7 @@ class NoviceGuideService extends GetxService { void startAIGuide() async { if (!isAIGuide) { - await Future.delayed(const Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 200)); isAIGuide = true; _storage.write('ai_guide', true); Intro.of(Get.context!).start(group: 'ai'); diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index ff5da53fd..d989cd037 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -934,21 +934,19 @@ class _AgentViewState extends State with WidgetsBindingObserver { Widget _buildPhoneCallOverlay(BuildContext context, bool isDarkMode) { return Container( color: Colors.transparent, // 设置整个覆盖层为透明 - child: SafeArea( - child: Column( - children: [ - // 中间空白区域 - 完全透明 - const Expanded( - child: SizedBox(), // 空的占位符,完全透明 - ), + child: Column( + children: [ + // 中间空白区域 - 完全透明 + const Expanded( + child: SizedBox(), // 空的占位符,完全透明 + ), - // 底部控制按钮 - 包含音频波纹 - Container( - color: isDarkMode ? Colors.black : Colors.white, - child: _buildPhoneCallControls(isDarkMode), - ), - ], - ), + // 底部控制按钮 - 包含音频波纹 + Container( + color: isDarkMode ? Colors.black : Colors.white, + child: _buildPhoneCallControls(isDarkMode), + ), + ], ), ); } From be2a8a1770f22a5b485a4579c6b3641d3f650524 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 1 Aug 2025 20:10:17 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E9=9A=90=E8=97=8F=E5=BC=95=E5=AF=BC?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/novice_guide_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart index 060bb5c29..aeb3baa39 100644 --- a/lib/data/services/novice_guide_service.dart +++ b/lib/data/services/novice_guide_service.dart @@ -5,8 +5,8 @@ import 'package:get_storage/get_storage.dart'; class NoviceGuideService extends GetxService { final GetStorage _storage = GetStorage(); - bool isHomeGuide = false; - bool isAIGuide = false; + bool isHomeGuide = true; + bool isAIGuide = true; @override void onInit() { From ed99e75ccee68afa63b7480be2a89da08bc92128 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 20:26:38 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat(=E9=85=8D=E5=AF=B9):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0iOS=E8=93=9D=E7=89=99=E5=BC=95=E5=AF=BC=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E5=B9=B6=E6=94=AF=E6=8C=81=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加蓝牙引导对话框的国际化文本 使用flutter_screenutil进行响应式布局 移除未使用的导入 --- lib/core/translations/language/en_us.dart | 13 +++++ lib/core/translations/language/zh_cn.dart | 9 +++ .../controllers/pairing_controller.dart | 56 +++++++++++-------- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index f529e2a7a..51d9bd764 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -813,6 +813,19 @@ const Map enUS = { 'deviceAuthorizationFailedCheckCode': 'Device authorization failed, please check authorization code', + ///iOS蓝牙引导窗 + 'bluetoothGuideTitle': 'Manual Bluetooth Activation Required', // 需要手动开启蓝牙 + 'bluetoothGuideSteps': 'Please follow these steps:', // 请按照以下步骤操作: + 'bluetoothGuideStep1': + 'Enable Bluetooth: Settings → Bluetooth → Turn On', // 开启蓝牙:设置 → 蓝牙 → 开启 + 'bluetoothGuideStep2': + 'Connect Device: Find your device in "My Devices" and tap to connect', // 连接设备:在"我的设备"中找到您的设备并点击连接 + 'bluetoothGuideStep3': + 'Return to app to continue pairing process', // 返回应用继续配对流程 + 'bluetoothGuideTip': + 'Tip: You can also long press the Bluetooth icon in Control Center to quickly access Bluetooth settings', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置 + 'iKnow': 'I Know', // 我知道了 + // 用户反馈页 "feedback": "Feedback", 'feedbackInfo': 'Provide feedback or suggestions', diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 52a3cc093..6bd1701d6 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -746,6 +746,15 @@ const Map zhCN = { 'bleConnectionFailed': 'BLE连接失败', 'deviceAuthorizationFailedCheckCode': '设备授权失败,请检查授权码', + ///iOS蓝牙引导窗 + 'bluetoothGuideTitle': '需要手动开启蓝牙', // 需要手动开启蓝牙 + 'bluetoothGuideSteps': '请按照以下步骤操作:', // 请按照以下步骤操作: + 'bluetoothGuideStep1': '开启蓝牙:设置 → 蓝牙 → 开启', // 开启蓝牙:设置 → 蓝牙 → 开启 + 'bluetoothGuideStep2': '连接设备:在"我的设备"中找到您的设备并点击连接', // 连接设备:在"我的设备"中找到您的设备并点击连接 + 'bluetoothGuideStep3': '返回应用继续配对流程', // 返回应用继续配对流程 + 'bluetoothGuideTip': '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置 + 'iKnow': '我知道了', // 我知道了 + //用户反馈页 "feedback": "用户反馈", 'feedbackInfo': '提供反馈或建议', diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index dedb8fbbb..c467e2776 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1,12 +1,10 @@ -import 'dart:convert'; -import 'dart:typed_data'; - import 'package:app_settings/app_settings.dart'; import 'package:voitrans/data/models/appconfig_model.dart'; import '/data/models/appconfig.dart'; import '/data/models/user_Info.dart'; import '/data/services/network/api.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'package:permission_handler/permission_handler.dart'; @@ -16,7 +14,6 @@ import 'dart:io'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/ble_manager.dart'; import '../../../core/utils/permission_util.dart'; -import 'package:url_launcher/url_launcher.dart'; /// 配对状态枚举 - 简化版本 enum PairingState { @@ -1028,52 +1025,65 @@ class PairingController extends GetxController { void _showBluetoothGuideDialog() { Get.dialog( AlertDialog( - title: Text('需要手动开启蓝牙'), + title: Text('bluetoothGuideTitle'.tr), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('请按照以下步骤操作:'), - SizedBox(height: 16), + Text('bluetoothGuideSteps'.tr), + SizedBox(height: 16.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('1. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('开启蓝牙:设置 → 蓝牙 → 开启')), + Text('1. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep1'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('2. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('连接设备:在"我的设备"中找到您的设备并点击连接')), + Text('2. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep2'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('3. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('返回应用继续配对流程')), + Text('3. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep3'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 16), + SizedBox(height: 16.h), Container( - padding: EdgeInsets.all(12), + padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.blue.withOpacity(0.3)), ), child: Row( children: [ - Icon(Icons.info_outline, color: Colors.blue, size: 20), - SizedBox(width: 8), + Icon(Icons.info_outline, color: Colors.blue, size: 20.sp), + SizedBox(width: 8.w), Expanded( child: Text( - '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', - style: TextStyle(fontSize: 12, color: Colors.blue[700]), + 'bluetoothGuideTip'.tr, + style: + TextStyle(fontSize: 12.sp, color: Colors.blue[700]), ), ), ], @@ -1084,7 +1094,7 @@ class PairingController extends GetxController { actions: [ TextButton( onPressed: () => Get.back(), - child: Text('我知道了'), + child: Text('iKnow'.tr, style: TextStyle(fontSize: 14.sp)), ), ], ),