import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'dart:convert'; import 'package:get_storage/get_storage.dart'; import '../../../data/services/azure_asr_service.dart'; import '../../../data/services/volcano_translation_service.dart'; import '../../../data/services/volcano_tts_api_service.dart'; import '../../../core/utils/logger.dart'; class TranslationController extends GetxController { // Services final AzureAsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = Get.find(); final VolcanoTtsApiService _ttsService = Get.find(); final GetStorage _storage = GetStorage(); // Storage key static const String _historyKey = 'translation_history'; static const int _maxHistoryItems = 100; // 最多保存100条历史记录 // State variables final isRecognizing = false.obs; final isTranslating = false.obs; final isTtsEnabled = true.obs; final sourceLanguage = '中文 (Chinese)'.obs; final targetLanguage = '英语 (English)'.obs; final translationHistory = [].obs; // 支持的语言列表 - 改为Map结构,键为显示名称,值为语言代码 final Map supportedLanguages = { '中文 (Chinese)': 'zh', '英语 (English)': 'en', '日语 (Japanese)': 'ja', '韩语 (Korean)': 'ko', '法语 (French)': 'fr', '德语 (German)': 'de', '西班牙语 (Spanish)': 'es', '意大利语 (Italian)': 'it', '俄语 (Russian)': 'ru', '泰语 (Thai)': 'th', '葡萄牙语 (Portuguese)': 'pt', '阿拉伯语 (Arabic)': 'ar', '越南语 (Vietnamese)': 'vi', '印尼语 (Indonesian)': 'id', '印地语 (Hindi)': 'hi', '土耳其语 (Turkish)': 'tr', '波兰语 (Polish)': 'pl', '荷兰语 (Dutch)': 'nl', '瑞典语 (Swedish)': 'sv', '捷克语 (Czech)': 'cs', '希腊语 (Greek)': 'el', '罗马尼亚语 (Romanian)': 'ro', '匈牙利语 (Hungarian)': 'hu', '乌克兰语 (Ukrainian)': 'uk', '保加利亚语 (Bulgarian)': 'bg', '丹麦语 (Danish)': 'da', '芬兰语 (Finnish)': 'fi', '挪威语 (Norwegian)': 'no', '马来语 (Malay)': 'ms', }; // Current translation item final Rx currentItem = Rx(null); // Subscription for recognition stream StreamSubscription? _recognitionSubscription; // Scroll controller for ListView final ScrollController scrollController = ScrollController(); // Timer for debouncing translation requests Timer? _translationDebounceTimer; // 当前会话ID String? currentSessionId; @override void onInit() { super.onInit(); // 加载历史记录 loadTranslationHistory(); // Initialize the translation service _initTranslationService(); // 添加后处理,确保在历史记录加载后滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { // 延迟执行滚动操作,确保视图已完全渲染 Future.delayed(const Duration(milliseconds: 300), () { _scrollToBottom(); }); }); // Start voice recognition automatically when the page is loaded startRecognition(); } @override void onClose() { stopRecognition(); _recognitionSubscription?.cancel(); _translationDebounceTimer?.cancel(); scrollController.dispose(); super.onClose(); } // Initialize the translation service Future _initTranslationService() async { try { await _translationService.initialize(); } catch (e) { Get.snackbar( 'Error', 'Failed to initialize translation service: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, ); } } // Toggle TTS feature void toggleTts() { isTtsEnabled.toggle(); } // Start voice recognition Future startRecognition() async { if (isRecognizing.value) return; try { // 生成新的会话ID currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); // Initialize a new translation item currentItem.value = TranslationItem( sourceText: '', translatedText: '', sourceLanguage: sourceLanguage.value, targetLanguage: targetLanguage.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: true, // 标记为会话中的第一条 ); // Start the voice recognition service final success = await _asrService.startContinuousRecognition(); if (success) { isRecognizing.value = true; // Reset the current item if (currentItem.value != null) { currentItem.value!.sourceText = ''; currentItem.value!.translatedText = ''; } // Listen for recognition events _recognitionSubscription = _asrService.recognitionStream?.listen((event) { switch (event.type) { case RecognitionEventType.finalResult: if (event.text.isNotEmpty) { handleFinalResult(event.text); } break; case RecognitionEventType.recognizing: // Update the UI with intermediate results if (event.text.isNotEmpty) { handleIntermediateResult(event.text); } break; case RecognitionEventType.error: // 错误处理,停止语音识别 Logger.error('语音识别错误: ${event.error}'); // 停止语音识别 isRecognizing.value = false; _asrService.stopContinuousRecognition(); break; default: break; } }); } else { throw Exception('Failed to start continuous recognition'); } } catch (e) { isRecognizing.value = false; Get.snackbar( 'Error', 'Failed to start voice recognition: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, ); } } // Stop voice recognition Future stopRecognition() async { if (!isRecognizing.value) return; try { await _asrService.stopContinuousRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; isRecognizing.value = false; // 重置当前会话ID currentSessionId = null; // 重置当前项 currentItem.value = null; } catch (e) { Get.snackbar('Error', '停止语音识别失败: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); } } // Handle intermediate recognition result void handleIntermediateResult(String text) { if (text.isEmpty) return; // 如果当前没有活跃的翻译项,创建一个新的 if (currentItem.value == null) { currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); currentItem.value = TranslationItem( sourceText: text, translatedText: '', sourceLanguage: sourceLanguage.value, targetLanguage: targetLanguage.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, ); currentItem.refresh(); } else { // 更新当前翻译项 currentItem.value!.sourceText = text; // 通知 GetX 更新 currentItem.refresh(); } // 防抖处理翻译请求 _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) { translateText(currentItem.value!.sourceText); } }); // 滚动到底部显示最新内容 _scrollToBottom(); } // Handle final recognition result void handleFinalResult(String text) async { // 如果没有文本或当前项为空,直接返回 if (text.isEmpty || currentItem.value == null) return; // 更新当前项的源文本 currentItem.value!.sourceText = text; currentItem.refresh(); // 添加到历史记录 translationHistory.add(currentItem.value!); saveTranslationHistory(); // 重置当前项 currentItem.value = null; // 对最终结果进行一次翻译,确保翻译结果是最新的 final translationResult = await translateText(text); if (translationResult != null) { if (isTtsEnabled.value) { await playTranslatedText(translationResult); } } // 滚动到底部显示最新内容 _scrollToBottom(); } // 检测文本语言 String detectLanguage(String text) { if (text.isEmpty) return sourceLanguage.value; // 检测各种语言特征 final bool containsChinese = RegExp(r'[\u4e00-\u9fa5]').hasMatch(text); final bool containsLatin = RegExp(r'[a-zA-Z]').hasMatch(text); final bool containsJapanese = RegExp(r'[\u3040-\u309F\u30A0-\u30FF]').hasMatch(text); final bool containsKorean = RegExp(r'[\uAC00-\uD7AF\u1100-\u11FF]').hasMatch(text); final bool containsCyrillic = RegExp(r'[\u0400-\u04FF]').hasMatch(text); final bool containsArabic = RegExp(r'[\u0600-\u06FF]').hasMatch(text); final bool containsThai = RegExp(r'[\u0E00-\u0E7F]').hasMatch(text); // 获取当前设置的源语言代码 final String sourceCode = supportedLanguages[sourceLanguage.value] ?? 'zh'; // 根据文本特征判断语言 if (containsChinese && sourceCode != 'zh') { return '中文 (Chinese)'; } else if (containsJapanese && sourceCode != 'ja') { return '日语 (Japanese)'; } else if (containsKorean && sourceCode != 'ko') { return '韩语 (Korean)'; } else if (containsCyrillic && sourceCode != 'ru') { return '俄语 (Russian)'; } else if (containsArabic && sourceCode != 'ar') { return '阿拉伯语 (Arabic)'; } else if (containsThai && sourceCode != 'th') { return '泰语 (Thai)'; } else if (containsLatin && sourceCode == 'zh') { return '英语 (English)'; } // 默认返回当前设置的源语言 return sourceLanguage.value; } // Translate the recognized text Future translateText(String sourceText) async { if (sourceText.isEmpty) return null; isTranslating.value = true; String? translationResult; try { // 检测源文本语言 final detectedLanguage = detectLanguage(sourceText); final bool shouldSwapLanguages = detectedLanguage != sourceLanguage.value; // 调用翻译服务 final result = await _translationService.translateText( text: sourceText, sourceLanguage: shouldSwapLanguages ? detectedLanguage : sourceLanguage.value, targetLanguage: shouldSwapLanguages ? sourceLanguage.value : targetLanguage.value, ); if (result != null) { translationResult = result; // 更新翻译结果 if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) { // 直接更新并刷新 currentItem.value!.translatedText = result; currentItem.refresh(); } else { // 在历史记录中查找匹配的项 final index = translationHistory.indexWhere( (historyItem) => historyItem.sourceText == sourceText); if (index != -1) { // 直接更新历史记录项 translationHistory[index].translatedText = result; // 通知 GetX 更新 translationHistory.refresh(); saveTranslationHistory(); } // 滚动到底部显示最新内容 _scrollToBottom(); } } return translationResult; } catch (e) { Get.snackbar('Error', '翻译失败: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); return null; } finally { isTranslating.value = false; } } // 播放翻译文本 Future playTranslatedText(String text) async { if (text.isEmpty) return; try { await _ttsService.speakSingle(text); } catch (e) { Get.snackbar('Error', '播放翻译失败: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); } } // 停止所有操作(语音识别和TTS) Future stopAll() async { try { // 停止语音识别 if (isRecognizing.value) { await stopRecognition(); } // 停止TTS播放 await _ttsService.stop(); } catch (e) { Get.snackbar('Error', '停止操作失败: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); } } // 播放特定翻译项 Future playTranslationItem(TranslationItem item) async { if (item.translatedText.isEmpty) return; await playTranslatedText(item.translatedText); } // 播放文本 Future playText(String text) async { if (text.isEmpty) return; await playTranslatedText(text); } // 滚动到列表底部 void _scrollToBottom() { if (!scrollController.hasClients) { Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); return; } try { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } catch (e) { // 如果滚动失败,尝试延迟后再次滚动 Future.delayed(const Duration(milliseconds: 100), () { if (scrollController.hasClients) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } }); } } // 切换源语言和目标语言 void swapLanguages() { final temp = sourceLanguage.value; sourceLanguage.value = targetLanguage.value; targetLanguage.value = temp; // 如果正在识别,需要停止并重新开始 if (isRecognizing.value) { stopRecognition(); startRecognition(); } } // 设置源语言 void setSourceLanguage(String language) { if (sourceLanguage.value != language) { sourceLanguage.value = language; // 如果正在识别,需要停止并重新开始 if (isRecognizing.value) { stopRecognition(); startRecognition(); } } } // 设置目标语言 void setTargetLanguage(String language) { if (targetLanguage.value != language) { targetLanguage.value = language; } } // Load translation history from storage void loadTranslationHistory() { final historyJson = _storage.read(_historyKey); if (historyJson != null) { try { final List historyList = jsonDecode(historyJson); translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); } catch (e) { // 如果解析失败,清除历史记录 _storage.remove(_historyKey); } } } // Save translation history to storage void saveTranslationHistory() { // 限制历史记录数量,保留最新的记录 if (translationHistory.length > _maxHistoryItems) { translationHistory.removeRange( 0, translationHistory.length - _maxHistoryItems); } final List> historyJson = translationHistory.map((item) => item.toJson()).toList(); _storage.write(_historyKey, jsonEncode(historyJson)); } // Clear translation history void clearTranslationHistory() { translationHistory.clear(); _storage.remove(_historyKey); } } // Model class for translation items class TranslationItem { String sourceText; String translatedText; final String sourceLanguage; final String targetLanguage; final DateTime timestamp; String sessionId; final bool isFirstInSession; TranslationItem({ required this.sourceText, required this.translatedText, required this.sourceLanguage, required this.targetLanguage, required this.timestamp, required this.sessionId, this.isFirstInSession = 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'] ?? '', sourceLanguage: json['sourceLanguage'] ?? '中文 (Chinese)', targetLanguage: json['targetLanguage'] ?? '英语 (English)', timestamp: timestamp, sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), isFirstInSession: json['isFirstInSession'] ?? false, ); } Map toJson() { return { 'sourceText': sourceText, 'translatedText': translatedText, 'sourceLanguage': sourceLanguage, 'targetLanguage': targetLanguage, 'timestamp': timestamp.toIso8601String(), 'sessionId': sessionId, 'isFirstInSession': isFirstInSession, }; } }