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 recognizedText = ''.obs; final translatedText = ''.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 TranslationItem? currentItem; // 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 = 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 recognized text recognizedText.value = ''; translatedText.value = ''; // 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: Get.snackbar( 'Error', 'Recognition error: ${event.error}', snackPosition: SnackPosition.BOTTOM, ); 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; // 添加当前项到历史(如果有内容) finalizeCurrentItem(); // 重置当前会话ID currentSessionId = null; // 重置当前显示的文本 recognizedText.value = ''; translatedText.value = ''; } catch (e) { Get.snackbar( 'Error', 'Failed to stop voice recognition: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, ); } } // Handle intermediate recognition result void handleIntermediateResult(String text) { if (text.isEmpty) return; // 如果当前没有活跃的翻译项,创建一个新的 if (currentItem == null) { // 如果没有当前会话ID,创建一个新的 if (currentSessionId == null) { currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); } currentItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguage: sourceLanguage.value, targetLanguage: targetLanguage.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, ); } else { // 更新当前翻译项 currentItem!.sourceText = text; } // 实时更新识别文本 recognizedText.value = text; // Debounce translation requests to avoid too many API calls _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { translateText(text, isFinal: false); }); // Scroll to the bottom to show the latest content _scrollToBottom(); } // Handle final recognition result void handleFinalResult(String text) { if (text.isEmpty) return; // 更新当前翻译项(如果不存在则创建一个) if (currentItem == null) { // 如果没有当前会话ID,创建一个新的 if (currentSessionId == null) { currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); } currentItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguage: sourceLanguage.value, targetLanguage: targetLanguage.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, ); } else { currentItem!.sourceText = text; } // 更新识别文本 recognizedText.value = text; // 翻译最终结果 (添加到历史的逻辑已移至translateText方法) translateText(text, isFinal: true).then((_) { // 作为安全措施,确保当前项被添加到历史中 // 这是为了处理translateText中可能因为条件不满足而没有添加到历史的情况 finalizeCurrentItem(); }); // Scroll to the bottom to show the latest content _scrollToBottom(); } // Finalize the current translation item and add it to history void finalizeCurrentItem() { if (currentItem != null && currentItem!.sourceText.isNotEmpty && currentItem!.translatedText.isNotEmpty && !translationHistory.contains(currentItem)) { translationHistory.add(currentItem!); saveTranslationHistory(); currentItem = null; } } // Handle voice recognition result (legacy method, kept for compatibility) void handleVoiceResult(String text) { handleFinalResult(text); } // Translate the recognized text Future translateText(String text, {bool isFinal = true}) async { if (text.isEmpty) return; isTranslating.value = true; // 保存当前的翻译项引用,确保异步返回时更新正确的项 final TranslationItem? itemToUpdate = currentItem; try { // 获取源语言和目标语言的代码 final String sourceCode = supportedLanguages[sourceLanguage.value] ?? 'zh'; // 尝试检测输入文本的语言(这里简化处理,实际项目可能需要更复杂的语言检测) bool shouldSwapLanguages = false; // 简单的语言检测逻辑 - 检查是否包含中文字符 final bool containsChinese = RegExp(r'[\u4e00-\u9fa5]').hasMatch(text); final bool containsLatin = RegExp(r'[a-zA-Z]').hasMatch(text); // 如果源语言是中文但文本不包含中文字符,且包含拉丁字符,可能需要交换语言 if (sourceCode == 'zh' && !containsChinese && containsLatin) { shouldSwapLanguages = true; } // 如果源语言不是中文但文本包含中文字符,可能需要交换语言 else if (sourceCode != 'zh' && containsChinese) { shouldSwapLanguages = true; } // 调用翻译服务 final result = await _translationService.translateText( text: text, sourceLanguage: shouldSwapLanguages ? targetLanguage.value : sourceLanguage.value, targetLanguage: shouldSwapLanguages ? sourceLanguage.value : targetLanguage.value, ); if (result != null) { // 实时更新翻译文本 translatedText.value = result; // 确保更新的是发起翻译请求时的翻译项,而不是当前可能已经变化的翻译项 if (itemToUpdate != null && itemToUpdate.sourceText == text) { itemToUpdate.translatedText = result; // 如果是最终结果,并且当前项仍然是发起请求时的项,则添加到历史 if (isFinal && itemToUpdate == currentItem) { // 检查是否已经存在相同会话ID的项 bool isFirstInSession = true; for (var item in translationHistory) { if (item.sessionId == itemToUpdate.sessionId) { isFirstInSession = false; break; } } // 创建一个新的TranslationItem,包含正确的isFirstInSession值 final updatedItem = TranslationItem( sourceText: itemToUpdate.sourceText, translatedText: itemToUpdate.translatedText, sourceLanguage: itemToUpdate.sourceLanguage, targetLanguage: itemToUpdate.targetLanguage, timestamp: itemToUpdate.timestamp, sessionId: itemToUpdate.sessionId, isFirstInSession: isFirstInSession, ); translationHistory.add(updatedItem); saveTranslationHistory(); currentItem = null; // 重要:将当前项设为null,表示已完成 // 重置当前显示的文本,避免与历史项重复 recognizedText.value = ''; translatedText.value = ''; } // Play TTS if enabled and this is a final result if (isTtsEnabled.value && isFinal) { playTranslatedText(result); } } } } catch (e) { Get.snackbar( 'Error', 'Translation failed: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, ); } finally { isTranslating.value = false; } } // Play the translated text using TTS Future playTranslatedText(String text) async { if (text.isEmpty) return; try { await _ttsService.speakSingle(text); } catch (e) { Get.snackbar( 'Error', 'Failed to play translation: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, ); } } // Play a specific translation item Future playTranslationItem(TranslationItem item) async { await playTranslatedText(item.translatedText); } // Scroll to the bottom of the list void _scrollToBottom() { if (scrollController.hasClients) { try { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } catch (e) { // 如果滚动失败,尝试延迟后再次滚动 Future.delayed(const Duration(milliseconds: 200), () { if (scrollController.hasClients) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } }); } } else { // 如果scrollController还没有客户端,延迟后再尝试 Future.delayed(const Duration(milliseconds: 200), _scrollToBottom); } } // 切换源语言和目标语言 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) { final List historyList = jsonDecode(historyJson); translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); // 确保在历史记录加载后滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } } // 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; final 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) { return TranslationItem( sourceText: json['sourceText'], translatedText: json['translatedText'], sourceLanguage: json['sourceLanguage'], targetLanguage: json['targetLanguage'], timestamp: DateTime.parse(json['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, }; } }