import 'dart:io'; import '../../../core/utils/permission_util.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'dart:convert'; import 'package:get_storage/get_storage.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../../data/services/music_manager.dart'; import '../../../data/services/volcano_translation_service.dart'; import '../../../data/services/tts_service.dart'; import '../../../data/services/language_manager.dart'; import '../../../core/utils/logger.dart'; import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/services/asr_service.dart'; import '../../../data/services/ble_manager.dart'; // 切换音频输出设备的枚举 enum AudioOutputDevice { defaultDevice, // 默认(如有耳机优先耳机,否则扬声器) headphones, // 强制耳机 speaker, // 强制扬声器 } class TranslationController extends GetxController { static const String TAG = 'TranslationController'; // 服务 final AsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = Get.find(); final TtsService _ttsService = Get.find(); final LanguageManager _languageManager = Get.find(); final GetStorage _storage = GetStorage(); // 蓝牙服务 final bleManager = Get.find(); //音乐播放管理 final _musiceManager = Get.find(); // 音频输入源 bool _audioSourceType = false; //是否开启播放 bool isPlayback = false; //录音计时器 final RxString recordDurationText = '00:00'.obs; //记录计时器 Timer? _recordTimer; //秒 int _seconds = 0; // 存储相关配置 static const String _historyKey = 'translation_history'; static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; // 【重要改进】区分存储限制和显示限制 // 存储限制:大幅增加到1000条,充分利用本地存储能力,几乎无限制存储 static const int _maxStorageItems = 1000; // 主页面显示限制:只显示最近20条,保证界面性能和用户体验 static const int _maxDisplayItems = 20; // 分页加载相关状态 final isLoadingMore = false.obs; // 是否正在加载更多历史记录 final hasMoreHistory = true.obs; // 是否还有更多历史记录 final currentPage = 1.obs; // 当前页码 final pageSize = 20; // 每页记录数量 final isLoadingMoreError = false.obs; // 加载更多时是否出错 bool _scrollListenerSetup = false; // 滚动监听器设置标志 RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2 RxInt lastActiveSpeaker = 0.obs; // 上一次状态 bool isContinuePlaying = false; // 是否继续播放 // 状态变量 final isRecognizing = false.obs; final isTranslating = false.obs; final isTtsEnabled = true.obs; final isRecording = false.obs; // 语言相关 final sourceLanguage = '中文(简体)'.obs; final targetLanguage = '英语'.obs; final sourceLanguageCode = 'zh-CN'.obs; final targetLanguageCode = 'en-US'.obs; var detectedLanguageCode = ''; final currentModeTitle = 'simultaneousTranslation'.obs; final currentMode = 'simultaneous'.obs; // 翻译历史和当前项 String? fTFTranslationResult = ''; final translationHistory = [].obs; String? currentSessionId; final appDocDir = getApplicationDocumentsDirectory(); var appDir; var dir; //int num = 0; Future changeTranslationMode(String mode) async { currentMode.value = mode; currentModeTitle.value = _getModeTitle(mode); stopAll(); } String _getModeTitle(String mode) { switch (mode) { case 'audioVideo': return 'audioVideoTranslation'; case 'call': return 'callTranslation'; case 'faceToFace': return 'faceToFaceTranslation'; default: return 'simultaneousTranslation'; } } Future setActiveSpeaker(int id) async { activeSpeaker.value = id; // 监听识别结果 if (activeSpeaker.value != 0) { isPlayback = false; final recognitionStream = await _asrService.startContinuousRecognition(_audioSourceType); lastActiveSpeaker.value = id; } else { if (fTFTranslationResult != '' && !isPlayback) { isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; } else if ((translationHistory.isEmpty || !translationHistory.last.isIntermediate) && currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } } } // 禁用蓝牙音频 void disableBluetoothAudio() => _asrService.disableBluetoothAudio(); //恢复原始音频状态 void restoreOriginalAudioState() => _asrService.restoreOriginalAudioState(); // 兼容旧版视图的计算属性 Rx get currentItem { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { return Rx(translationHistory.last); } return Rx(null); } // UI相关 final ScrollController scrollController = ScrollController(); // 其他 StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; Timer? _translationDebounceTimer; // 获取支持的语言列表 Map get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); @override void onInit() { super.onInit(); //_setupEventListeners(); // 加载上次保存的语言设置 _loadSavedLanguages(); // 初始化语言代码 final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); if (sourceAsrCode != null) { sourceLanguageCode.value = sourceAsrCode; } final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); if (targetAsrCode != null) { targetLanguageCode.value = targetAsrCode; } // 加载历史记录 loadTranslationHistory(); // 初始化服务 _initServices(); // 🔑 reverse模式下不需要手动滚动,ListView会自然显示最新内容 // 参考Agent模块的实现模式 } // 加载保存的语言设置 void _loadSavedLanguages() { final savedSourceLanguage = _storage.read(_sourceLanguageKey); final savedTargetLanguage = _storage.read(_targetLanguageKey); if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) { sourceLanguage.value = savedSourceLanguage; } if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) { targetLanguage.value = savedTargetLanguage; } } // 保存当前语言设置 void _saveLanguageSettings() { _storage.write(_sourceLanguageKey, sourceLanguage.value); _storage.write(_targetLanguageKey, targetLanguage.value); } Future _requestRecordPermission() async { try { final bool granted = await PermissionUtil.instance.requestPermission( permissionType: Permission.microphone, permissionName: "录音", explanationText: "我们需要访问麦克风权限,以便您录制音频内容。", permanentDenialText: "检测到您已永久拒绝录音权限。如需继续使用此功能,请前往应用设置中手动开启麦克风权限。", ); if (granted) { Logger.d("Permission", "用户授予了录音权限"); return true; } else { Logger.d("Permission", "用户未授予录音权限"); return false; } } catch (e) { Logger.e("Permission", "请求录音权限时出错: $e"); return false; } } // 请求存储权限 Future _requestStoragePermission() async { // final BuildContext context = Get.overlayContext!; try { final bool granted = await PermissionUtil.instance.requestPermission( permissionType: Permission.storage, permissionName: "存储", explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", ); if (granted) { Logger.d("Permission", "用户授予了存储权限"); return true; } else { Logger.d("Permission", "用户未授予存储权限"); return true; } } catch (e) { Logger.e("Permission", "请求存储权限时出错: $e"); return false; } } // 暂停其他服务 Future suspendOtherServices() async { try { var status = await _musiceManager.getStatus(); var isPlaying = status['isPlaying'] ?? false; if (isPlaying == true) { isContinuePlaying = true; await _musiceManager.pause(); // 暂停音乐 } else { isContinuePlaying = false; } } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } // 恢复其他服务 Future restoreOtherServices() async { try { if (isContinuePlaying) await _musiceManager.resume(); // 暂停音乐 } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } // 初始化服务 Future _initServices() async { try { await _translationService.initialize(); // 初始化 ASR 服务时,明确指定需要支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); // 获取识别事件流 - 这启动了异步识别过程 var recognitionStream = await _asrService.recognizeCallback(); // 监听识别结果 _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); final bool storage = await _requestStoragePermission(); if (!storage) { Logger.d("Permission", "未授予存储权限,无法提供录音功能"); return; } appDir = await appDocDir; // 先 await 获取目录 dir = Directory("${appDir.path}/TranslatAudio"); if (!await dir.exists()) { await dir.create(recursive: true); // Add recursive creation } Logger.info('所有服务初始化完成'); } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } @override void onClose() { stopRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; _voiceInteractionSubscription?.cancel(); _translationDebounceTimer?.cancel(); scrollController.dispose(); restoreOtherServices(); super.onClose(); } // 切换TTS功能 void toggleTts() => isTtsEnabled.toggle(); // 切换录音功能 Future toggleisRecord() async { isRecording.toggle(); if (isRecording.value && isRecognizing.value) { startRecording(); } else if (!isRecording.value) { stopRecording(); } else {} } Future startRecording() async { final bool storage = await _requestStoragePermission(); if (!storage) { Logger.d("Permission", "未授予存储权限,无法提供录音功能"); return; } //isRecording.value = true; _seconds = 0; recordDurationText.value = '00:00'; _recordTimer = Timer.periodic(Duration(seconds: 1), (_) { _seconds++; final minutes = (_seconds ~/ 60).toString().padLeft(2, '0'); final seconds = (_seconds % 60).toString().padLeft(2, '0'); recordDurationText.value = '$minutes:$seconds'; }); // 启动实际录音逻辑 // ... final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); await _asrService.enableRecord( "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); } Future stopRecording() async { _recordTimer?.cancel(); _recordTimer = null; isRecording.value = false; recordDurationText.value = '00:00'; await _asrService.stopRecord(true); // 停止实际录音逻辑 // ... } // 开始语音识别 Future startRecognition() async { Logger.info('开始语音识别'); // 等待权限请求完成并获取结果 final bool record = await _requestRecordPermission(); // 如果权限被拒绝,直接返回 if (!record) { Logger.d("Permission", "录音权限被拒绝,无法开始录音"); return; } if (isRecognizing.value) return; try { // 每次开始识别前检查 ASR 服务是否已配置为当前语言对 final List currentLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages); if (needsReinit) { Logger.info('识别开始前发现语言变更,重新初始化ASR服务'); await _reinitializeAsrService(); } currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); // 开始连续语音识别 if (currentMode.value == "audioVideo") { bleManager.openDecoder(); // 发送ble音乐或者通话远端声音 _audioSourceType = true; isTtsEnabled.value = false; // 禁用TTS以避免干扰 Logger.info('发送ble音乐或者通话远端声音'); } else if (currentMode.value == "call") { // 发送ble系统mic和dac(音乐或者通话远端)声音 bleManager.openA2DPDecoder(); _audioSourceType = true; isTtsEnabled.value = false; Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音'); } else { // 开始连续语音识别 _audioSourceType = false; isTtsEnabled.value = true; } if (currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } else { await _ttsService.setAudioOutputDevice(0); final recognitionStream = await _asrService.startContinuousRecognition(_audioSourceType); } isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { startRecording(); } } catch (e) { isRecognizing.value = false; Logger.error('启动语音识别失败: ${e.toString()}'); } } // 处理识别事件 void _handleRecognitionEvent(RecognitionEvent event) { if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; handleFinalResult(event.text); } else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) { if (event.detectedLanguage.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; } handleIntermediateResult(event.text); } else if (event.type == RecognitionEventType.error) { isRecognizing.value = false; Logger.error('语音识别错误: ${event.error}'); } else if (event.type == RecognitionEventType.canceled) { isRecognizing.value = false; Logger.error('语音识别取消: ${event.error}'); } } // 停止语音识别 Future stopRecognition() async { if (!isRecognizing.value) return; try { restoreOriginalAudioState(); await _asrService.stopContinuousRecognition(); isRecognizing.value = false; // 如果当前有中间结果,将其作为最终结果处理 if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { // 标记为最终结果 translationHistory.last.isIntermediate = false; translationHistory.refresh(); // 如果还没有翻译结果,尝试翻译 if (translationHistory.last.translatedText.isEmpty) { await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true); } saveTranslationHistory(); } stopRecording(); currentSessionId = null; } catch (e) { Logger.error('停止语音识别失败: ${e.toString()}'); } } // 处理中间识别结果 void handleIntermediateResult(String text) { if (text.isEmpty) return; if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) { currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: sourceLanguageCode.value, targetLanguageCode: targetLanguageCode.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isIntermediate: true, ); // 添加新项后滚动到底部 if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { _asrService.stopContinuousRecognition(); } else { translationHistory.add(newItem); } _scrollToBottom(); } else { // 更新当前项 translationHistory.last.sourceText = text; translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); } // 防抖处理翻译请求 _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false); } }); } // 处理最终识别结果 Future handleFinalResult(String text) async { if (text.isEmpty || translationHistory.isEmpty) return; // 更新当前项 if (translationHistory.last.isIntermediate) { translationHistory.last.sourceText = text; translationHistory.last.isIntermediate = false; translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); saveTranslationHistory(); if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } // 使用项目的时间戳进行翻译 translateText(text, translationHistory.last.timestamp, isFinal: true); } else { // 如果最后一项不是中间结果,创建新项 final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: sourceLanguageCode.value, targetLanguageCode: targetLanguageCode.value, timestamp: DateTime.now(), sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(), isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isIntermediate: false, ); if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { _asrService.stopContinuousRecognition(); } //else { translationHistory.add(newItem); translationHistory.refresh(); _scrollToBottom(); saveTranslationHistory(); // 使用新项目的时间戳进行翻译 translateText(text, newItem.timestamp, isFinal: true); // } } } // 翻译文本 Future translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async { if (sourceText.isEmpty) return null; isTranslating.value = true; String? translationResult; try { String detectedSourceLanguageCode; String detectedTargetLanguageCode; if ((currentMode.value == 'faceToFace' && activeSpeaker.value == 1) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 1)) { detectedLanguageCode = sourceLanguageCode.value; detectedSourceLanguageCode = sourceLanguageCode.value; detectedTargetLanguageCode = targetLanguageCode.value; } else if ((currentMode.value == 'faceToFace' && activeSpeaker.value == 2) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 2)) { // 根据检测到的语言确定源语言和目标语言 detectedLanguageCode = targetLanguageCode.value; detectedSourceLanguageCode = targetLanguageCode.value; detectedTargetLanguageCode = sourceLanguageCode.value; } else { // 根据检测到的语言确定源语言和目标语言 final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; } // 调用翻译服务 translationResult = await _translationService.translateText( text: sourceText, sourceLanguageCode: detectedSourceLanguageCode, targetLanguageCode: detectedTargetLanguageCode, ); /// } if (translationResult != null) { // 查找匹配的项目 - 优先使用timestamp查找 int index = -1; index = translationHistory .indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp)); if (index != -1) { // 更新历史项 translationHistory[index].translatedText = translationResult; translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; translationHistory.refresh(); // 【改进】只有在最终结果时才保存,避免中间态时的频繁保存 if (isFinal) { saveTranslationHistory(); } // 更新后滚动到底部 _scrollToBottom(); // 播放TTS - 只在最终结果时播放 if (isFinal) { if (isTtsEnabled.value && currentMode.value != 'faceToFace') { await playTranslatedText(translationResult); } else { fTFTranslationResult = (fTFTranslationResult ?? '') + translationResult; print("translationResult=$translationResult"); if (activeSpeaker.value == 0 && !isPlayback) { isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; } } } } else { Logger.info('未找到匹配的历史项 $sourceText ${timestamp.toIso8601String()}'); } } } catch (e) { Logger.error('翻译失败: ${e.toString()}'); } finally { isTranslating.value = false; } return translationResult; } // 播放翻译文本 Future playTranslatedText(String text) async { if (text.isEmpty) return; try { final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value; final detectedTargetLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; final voiceName = _languageManager .getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? 'en-US-AriaNeural'; print("voiceName=$voiceName,text$text"); await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(text); } catch (e) { Logger.error('播放翻译失败: ${e.toString()}'); } } // 停止所有操作 Future stopAll() async { try { if (currentMode.value == "audioVideo" || currentMode.value == "call") { bleManager.closeCodec(); // 发送ble音乐或者通话远端声音 Logger.info('发送ble音乐或者通话远端声音'); } if (isRecognizing.value) { await stopRecognition(); } stopRecording(); _translationDebounceTimer?.cancel(); await _ttsService.stop(); } catch (e) { Logger.error('停止操作失败: ${e.toString()}'); } } // 播放特定翻译项 Future playTranslationItem(TranslationItem item) async { if (item.translatedText.isEmpty) return; try { final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(item.translatedText); } catch (e) { Logger.error('播放翻译项失败: ${e.toString()}'); } } // 播放文本 Future playText(String text, {String? languageCode}) async { if (text.isEmpty) return; try { final voiceName = languageCode != null ? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural' : _languageManager .getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(text); } catch (e) { Logger.error('播放文本失败: ${e.toString()}'); } } // 滚动到底部(适配reverse模式) void _scrollToBottom({bool animate = true}) { if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { // 🔑 reverse模式下:滚动到顶部(position = 0)来显示最新消息 // 正常模式下:滚动到底部(maxScrollExtent)来显示最新消息 const targetPosition = 0.0; // reverse模式下最新消息在顶部 if (animate) { // 有动画的滚动,用于新消息添加时 scrollController.animateTo( targetPosition, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } else { // 无动画的跳转,用于初始化时 scrollController.jumpTo(targetPosition); } } catch (e) { Logger.error('滚动到顶部失败(reverse模式): ${e.toString()}'); } } } // 设置滚动监听器(简化版,不再需要实际监听) void setupScrollListener() { // 简化后的空方法,保留接口兼容性 } // 底部控制栏高度 double _bottomBarHeight = 0.0; // 设置底部控制栏高度 void setBottomBarHeight(double height) { _bottomBarHeight = height; } // 获取底部控制栏高度 double get bottomBarHeight => _bottomBarHeight; // 切换源语言和目标语言 void swapLanguages() { stopAll(); final tempName = sourceLanguage.value; sourceLanguage.value = targetLanguage.value; targetLanguage.value = tempName; final tempCode = sourceLanguageCode.value; sourceLanguageCode.value = targetLanguageCode.value; targetLanguageCode.value = tempCode; // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } // 设置源语言 void setSourceLanguage(String language) { if (sourceLanguage.value != language) { stopAll(); sourceLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { sourceLanguageCode.value = asrCode; } // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } } // 设置目标语言 void setTargetLanguage(String language) { if (targetLanguage.value != language) { stopAll(); targetLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { targetLanguageCode.value = asrCode; } // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } } // 重新初始化ASR服务以支持新的语言 Future _reinitializeAsrService() async { try { // 更新ASR支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); // 重新初始化ASR服务 await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); // 获取识别事件流 - 这启动了异步识别过程 var recognitionStream = await _asrService.recognizeCallback(); // 监听识别结果 _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); Logger.info('ASR服务重新初始化成功'); } catch (e) { Logger.error('重新初始化ASR服务失败: ${e.toString()}'); } } // 加载翻译历史 void loadTranslationHistory() { final historyJson = _storage.read(_historyKey); if (historyJson != null) { try { final List historyList = jsonDecode(historyJson); final List allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); // 【注意】存储中的记录都是完成态记录(isIntermediate = false) // 因为保存时已经过滤掉了中间态记录,确保数据质量 // 初始化分页状态 currentPage.value = 1; isLoadingMore.value = false; isLoadingMoreError.value = false; // 主页面只显示最近的20条完成态记录,保证界面性能 if (allHistory.length > _maxDisplayItems) { translationHistory.value = allHistory.sublist(allHistory.length - _maxDisplayItems); // 检查是否还有更多历史记录 hasMoreHistory.value = true; } else { translationHistory.value = allHistory; hasMoreHistory.value = false; } // 设置滚动监听器 _setupScrollListener(); // 🔑 reverse模式下不需要滚动到底部,ListView会自然显示最新记录 // 参考Agent模块:使用reverse:true时不需要手动滚动到底部 } catch (e) { _storage.remove(_historyKey); Logger.error('加载翻译历史失败: ${e.toString()}'); } } else { // 没有历史数据时也要初始化分页状态 hasMoreHistory.value = false; currentPage.value = 1; isLoadingMore.value = false; isLoadingMoreError.value = false; } } // 保存翻译历史 - 回归原始简单实现 // 【修复】完全回归原始保存逻辑,只保存当前内存中的完成态记录 void saveTranslationHistory() { try { // 只保存当前内存中的完成态记录,过滤掉中间态记录 final List finalRecords = translationHistory.where((item) => !item.isIntermediate).toList(); // 应用存储限制,保留最新的1000条完成态记录 List historyToSave = finalRecords; if (finalRecords.length > _maxStorageItems) { historyToSave = finalRecords.sublist(finalRecords.length - _maxStorageItems); } // 保存到本地存储 final List> historyJson = historyToSave.map((item) => item.toJson()).toList(); _storage.write(_historyKey, jsonEncode(historyJson)); Logger.info('翻译历史保存成功: 存储${historyToSave.length}条记录'); } catch (e) { Logger.error('保存翻译历史失败: ${e.toString()}'); } } // 设置滚动监听器(适配reverse模式) void _setupScrollListener() { if (_scrollListenerSetup) return; // 防止重复设置 scrollController.addListener(() { // 🔑 reverse模式下:检查是否滚动到底部(实际是历史记录的顶部) // 参考Agent模块的实现 if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) { // 当距离底部100像素时触发加载更多历史记录 if (hasMoreHistory.value && !isLoadingMore.value) { Logger.d(TAG, '触发加载更多翻译历史记录(reverse模式)'); loadMoreHistory(); } } }); _scrollListenerSetup = true; } // 加载更多历史记录(分页加载) Future loadMoreHistory() async { // 防止重复加载 if (isLoadingMore.value || !hasMoreHistory.value) { Logger.d(TAG, '跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreHistory=${hasMoreHistory.value}'); return; } try { isLoadingMore.value = true; isLoadingMoreError.value = false; final nextPage = currentPage.value + 1; Logger.i(TAG, '正在加载第 $nextPage 页翻译历史...'); // 从存储中获取所有历史记录 final historyJson = _storage.read(_historyKey); if (historyJson != null) { final List historyList = jsonDecode(historyJson); final List allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); // 计算分页参数 final totalItems = allHistory.length; final totalPages = (totalItems / pageSize).ceil(); // 修复分页索引计算:从最新记录向前分页 // 第1页:最新20条 (index: totalItems-20 到 totalItems) // 第2页:接下来20条 (index: totalItems-40 到 totalItems-20) // 第3页:再接下来20条 (index: totalItems-60 到 totalItems-40) final endIndex = totalItems - ((nextPage - 1) * pageSize); final startIndex = totalItems - (nextPage * pageSize); Logger.d(TAG, '分页计算: 总记录=$totalItems, 总页数=$totalPages, 当前页=$nextPage, startIndex=$startIndex, endIndex=$endIndex'); if (endIndex > 0) { // 获取当前页的数据,确保索引不越界 final actualStartIndex = startIndex < 0 ? 0 : startIndex; final actualEndIndex = endIndex > totalItems ? totalItems : endIndex; final pageItems = allHistory.sublist(actualStartIndex, actualEndIndex); Logger.d(TAG, '实际索引: start=$actualStartIndex, end=$actualEndIndex, 获取${pageItems.length}条记录'); if (pageItems.isNotEmpty) { // 🔑 简化延迟,参考Agent模块(300ms vs 1000ms) await Future.delayed(const Duration(milliseconds: 300)); // 🔑 reverse模式下:将新记录插入到现有记录列表的开头 // 由于reverse显示,用户看到的顺序不会改变,无需复杂的位置保持 final updatedHistory = [...pageItems, ...translationHistory]; translationHistory.assignAll(updatedHistory); // 更新分页状态 currentPage.value = nextPage; // 修复hasMoreHistory判断:当startIndex <= 0时表示已经到达最早的记录 hasMoreHistory.value = startIndex > 0; Logger.i(TAG, '成功加载 ${pageItems.length} 条历史记录,当前页: $nextPage/$totalPages, 还有更多: ${hasMoreHistory.value}'); } else { // 没有更多记录 hasMoreHistory.value = false; Logger.i(TAG, '没有更多翻译历史记录'); } } else { // 索引超出范围,没有更多记录 hasMoreHistory.value = false; Logger.i(TAG, '已到达历史记录开头'); } } else { // 没有存储数据 hasMoreHistory.value = false; Logger.i(TAG, '存储中没有更多历史记录'); } } catch (e) { Logger.e(TAG, '加载更多历史记录失败: $e'); isLoadingMoreError.value = true; } finally { isLoadingMore.value = false; } } // 获取所有完成态历史记录(供历史页面使用) // 【修复】简化实现,合并存储和内存中的完成态记录 List getAllHistory() { try { final List result = []; final Set existingIds = {}; // 添加存储的历史记录(存储中的记录都是完成态) final historyJson = _storage.read(_historyKey); if (historyJson != null) { final List historyList = jsonDecode(historyJson); final List storedHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); for (final item in storedHistory) { final id = '${item.sessionId}_${item.timestamp.millisecondsSinceEpoch}'; if (!existingIds.contains(id)) { existingIds.add(id); result.add(item); } } } // 添加当前内存中的完成态记录(排除中间态记录) for (final item in translationHistory) { if (!item.isIntermediate) { final id = '${item.sessionId}_${item.timestamp.millisecondsSinceEpoch}'; if (!existingIds.contains(id)) { existingIds.add(id); result.add(item); } } } // 按时间排序 result.sort((a, b) => a.timestamp.compareTo(b.timestamp)); return result; } catch (e) { Logger.error('获取完整历史记录失败: ${e.toString()}'); // 发生错误时,返回当前内存中的完成态记录 return translationHistory.where((item) => !item.isIntermediate).toList(); } } // 清除翻译历史 void clearTranslationHistory() { translationHistory.clear(); _storage.remove(_historyKey); } // 删除指定会话的所有翻译记录 void deleteSessionHistory(String sessionId) { try { // 1. 从内存中删除该会话的所有记录 final originalCount = translationHistory.length; translationHistory.removeWhere((item) => item.sessionId == sessionId); final removedFromMemory = originalCount - translationHistory.length; // 2. 从本地存储中删除该会话的记录 final historyJson = _storage.read(_historyKey); int removedFromStorage = 0; if (historyJson != null) { final List historyList = jsonDecode(historyJson); final List storedHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); final originalStoredCount = storedHistory.length; storedHistory.removeWhere((item) => item.sessionId == sessionId); removedFromStorage = originalStoredCount - storedHistory.length; // 重新保存更新后的存储数据 final List> updatedHistoryJson = storedHistory.map((item) => item.toJson()).toList(); _storage.write(_historyKey, jsonEncode(updatedHistoryJson)); } // 3. 刷新UI显示 translationHistory.refresh(); Logger.info( '删除会话历史成功: sessionId=$sessionId, 内存删除${removedFromMemory}条, 存储删除${removedFromStorage}条'); } catch (e) { Logger.error('删除会话历史失败: ${e.toString()}'); rethrow; // 重新抛出异常,让调用方处理 } } // 删除指定的单条翻译记录 void deleteTranslationItem(String sessionId, DateTime timestamp) { try { // 1. 从内存中删除指定的翻译记录 final originalCount = translationHistory.length; translationHistory.removeWhere((item) => item.sessionId == sessionId && item.timestamp.millisecondsSinceEpoch == timestamp.millisecondsSinceEpoch); final removedFromMemory = originalCount - translationHistory.length; // 2. 从本地存储中删除指定的翻译记录 final historyJson = _storage.read(_historyKey); int removedFromStorage = 0; if (historyJson != null) { final List historyList = jsonDecode(historyJson); final List storedHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); final originalStoredCount = storedHistory.length; storedHistory.removeWhere((item) => item.sessionId == sessionId && item.timestamp.millisecondsSinceEpoch == timestamp.millisecondsSinceEpoch); removedFromStorage = originalStoredCount - storedHistory.length; // 重新保存更新后的存储数据 final List> updatedHistoryJson = storedHistory.map((item) => item.toJson()).toList(); _storage.write(_historyKey, jsonEncode(updatedHistoryJson)); } // 3. 刷新UI显示 translationHistory.refresh(); Logger.info( '删除单条翻译记录成功: sessionId=$sessionId, timestamp=${timestamp.toIso8601String()}, 内存删除${removedFromMemory}条, 存储删除${removedFromStorage}条'); } catch (e) { Logger.error('删除单条翻译记录失败: ${e.toString()}'); rethrow; // 重新抛出异常,让调用方处理 } } // 滚动到底部 (公共方法,用于向后兼容) void scrollToBottom({bool animate = true}) { _scrollToBottom(animate: animate); } } // 翻译项模型 class TranslationItem { String sourceText; String translatedText; String sourceLanguageCode; String targetLanguageCode; final DateTime timestamp; String sessionId; final bool isFirstInSession; bool isIntermediate; // 是否为中间结果 TranslationItem({ required this.sourceText, required this.translatedText, required this.sourceLanguageCode, required this.targetLanguageCode, required this.timestamp, required this.sessionId, this.isFirstInSession = false, this.isIntermediate = false, }); factory TranslationItem.fromJson(Map json) { DateTime timestamp; try { timestamp = DateTime.parse(json['timestamp']); } catch (e) { timestamp = DateTime.now(); } return TranslationItem( sourceText: json['sourceText'] ?? '', translatedText: json['translatedText'] ?? '', sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN', targetLanguageCode: json['targetLanguageCode'] ?? 'en-US', timestamp: timestamp, sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), isFirstInSession: json['isFirstInSession'] ?? false, isIntermediate: false, // 从存储加载的都是最终结果 ); } Map toJson() { return { 'sourceText': sourceText, 'translatedText': translatedText, 'sourceLanguageCode': sourceLanguageCode, 'targetLanguageCode': targetLanguageCode, 'timestamp': timestamp.toIso8601String(), 'sessionId': sessionId, 'isFirstInSession': isFirstInSession, // 不保存isIntermediate,因为存储的都是最终结果 }; } // 获取源语言的中文名称 String get sourceLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? '未知语言'; } // 获取目标语言的中文名称 String get targetLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? '未知语言'; } }