Browse Source

Merge branch 'ble' of github.com:deepcloud2048/deep_voice into ble

weicu
wolfplus 1 year ago
parent
commit
ac0190536a
  1. 9
      lib/core/bindings/initial_binding.dart
  2. 18
      lib/core/translations/app_translations.dart
  3. 6
      lib/core/translations/language/de_de.dart
  4. 6
      lib/core/translations/language/en_us.dart
  5. 6
      lib/core/translations/language/es_es.dart
  6. 6
      lib/core/translations/language/fr_fr.dart
  7. 426
      lib/core/translations/language/hi_in.dart
  8. 426
      lib/core/translations/language/id_id.dart
  9. 6
      lib/core/translations/language/it_it.dart
  10. 6
      lib/core/translations/language/ja_jp.dart
  11. 6
      lib/core/translations/language/ko_kr.dart
  12. 426
      lib/core/translations/language/ms_my.dart
  13. 431
      lib/core/translations/language/pl_pl.dart
  14. 6
      lib/core/translations/language/pt_pt.dart
  15. 6
      lib/core/translations/language/ru_ru.dart
  16. 423
      lib/core/translations/language/th_th.dart
  17. 427
      lib/core/translations/language/tr_tr.dart
  18. 6
      lib/core/translations/language/vi_vn.dart
  19. 6
      lib/core/translations/language/zh_cn.dart
  20. 6
      lib/core/translations/language/zh_hk.dart
  21. 4
      lib/data/models/appconfig.dart
  22. 4
      lib/data/services/asr_service.dart
  23. 13
      lib/data/services/ble_manager.dart
  24. 44
      lib/data/services/music_manager.dart
  25. 9
      lib/data/services/network/api.dart
  26. 7
      lib/data/services/speech_impl/azure_asr_service.dart
  27. 3
      lib/data/services/speech_impl/flutter_asr_service.dart
  28. 8
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  29. 10
      lib/data/services/speech_impl/volcano_asr_service.dart
  30. 3
      lib/data/services/speech_impl/xunfei_asr_service.dart
  31. 207
      lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
  32. 246
      lib/modules/agent/controllers/agent_controller.dart
  33. 104
      lib/modules/agent/views/agent_view.dart
  34. 254
      lib/modules/agent/views/message_bubble.dart
  35. 14
      lib/modules/chat/controllers/chat_controller.dart
  36. 12
      lib/modules/home/views/home_view.dart
  37. 4
      lib/modules/login/controllers/login_controller.dart
  38. 1
      lib/modules/login/views/login_view.dart
  39. 3
      lib/modules/meeting/controllers/meeting_record_controller.dart
  40. 9
      lib/modules/ota_upgrade/bindings/ota_upgrade_binding.dart
  41. 213
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  42. 54
      lib/modules/ota_upgrade/ota.dart
  43. 109
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  44. 79
      lib/modules/profile/controllers/profile_controller.dart
  45. 797
      lib/modules/profile/views/profile_view.dart
  46. 92
      lib/modules/settings/controllers/settings_controller.dart
  47. 307
      lib/modules/settings/views/settings_view.dart
  48. 150
      lib/modules/speech_test/controllers/speech_test_controller.dart
  49. 21
      lib/modules/splash/controllers/splash_controller.dart
  50. 69
      lib/modules/splash/views/splash_view.dart
  51. 44
      lib/modules/translation/controllers/translation_controller.dart
  52. 56
      lib/routes/app_pages.dart
  53. 2
      lib/routes/app_routes.dart
  54. 1
      local_plugins/agent_service/android/build.gradle.kts
  55. 4
      local_plugins/agent_service/android/settings.gradle.kts
  56. 89
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  57. 1
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  58. 3
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
  59. 2
      local_plugins/agent_service/pubspec.yaml
  60. 3
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  61. 39
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  62. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  63. 105
      local_plugins/ble_service/lib/ble_service.dart
  64. 33
      local_plugins/music_service/.gitignore
  65. 33
      local_plugins/music_service/.metadata
  66. 3
      local_plugins/music_service/CHANGELOG.md
  67. 1
      local_plugins/music_service/LICENSE
  68. 15
      local_plugins/music_service/README.md
  69. 4
      local_plugins/music_service/analysis_options.yaml
  70. 9
      local_plugins/music_service/android/.gitignore
  71. 70
      local_plugins/music_service/android/build.gradle
  72. 1
      local_plugins/music_service/android/settings.gradle
  73. 17
      local_plugins/music_service/android/src/main/AndroidManifest.xml
  74. 204
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  75. 59
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt
  76. 30
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt
  77. 38
      local_plugins/music_service/ios/.gitignore
  78. 0
      local_plugins/music_service/ios/Assets/.gitkeep
  79. 19
      local_plugins/music_service/ios/Classes/MusicServicePlugin.swift
  80. 14
      local_plugins/music_service/ios/Resources/PrivacyInfo.xcprivacy
  81. 29
      local_plugins/music_service/ios/music_service.podspec
  82. 62
      local_plugins/music_service/lib/music_service.dart
  83. 72
      local_plugins/music_service/pubspec.yaml
  84. 2
      pubspec.yaml

9
lib/core/bindings/initial_binding.dart

@ -1,3 +1,4 @@
import 'package:deep_voice/data/services/music_manager.dart';
import 'package:get/get.dart';
import '../../data/services/speech_factory.dart';
import '../../data/services/chat_history_service.dart';
@ -8,6 +9,7 @@ import '../../core/utils/logger.dart';
import '../../data/services/open_ai_service_adapter.dart';
import '../../data/services/bluetooth_manager.dart';
import '../../data/services/ble_manager.dart';
/// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings {
@override
@ -34,23 +36,20 @@ class InitialBinding extends Bindings {
adapter.initialize();
return adapter;
}, fenix: true);
// 蓝牙服务
Get.put<BluetoothManager>(BluetoothManager(), permanent: true);
// BleManager
Get.put<BleManager>(BleManager(), permanent: true);
// MusicManager
Get.put<MusicManager>(MusicManager(), permanent: true);
// 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
// 蓝牙媒体按钮服务
// Get.lazyPut<BluetoothMediaButtonService>(
// () => BluetoothMediaButtonService(),
// fenix: true);
// // 语音交互服务
// Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),

18
lib/core/translations/app_translations.dart

@ -15,6 +15,12 @@ import 'language/de_de.dart'; // 添加德语导入
import 'language/ru_ru.dart'; // 添加俄语导入
import 'language/vi_vn.dart'; // 添加越南语导入
import 'language/it_it.dart'; // 添加意大利语导入
import 'language/hi_in.dart'; // 添加印度语导入
import 'language/id_id.dart'; // 添加印尼语导入
import 'language/th_Th.dart'; // 添加泰语导入
import 'language/ms_MY.dart'; // 添加马来语导入
import 'language/tr_TR.dart'; // 添加土耳其语导入
import 'language/pl_PL.dart'; // 添加波兰语导入
class AppTranslations extends Translations {
static const fallbackLocale = Locale('zh', 'CN');
@ -31,6 +37,12 @@ class AppTranslations extends Translations {
const Locale('ru', 'RU'), // 添加俄语支持
const Locale('vi', 'VN'), // 添加越南语支持
const Locale('it', 'IT'), // 添加意大利语支持
const Locale('hi', 'IN'), // 添加印度语支持
const Locale('id', 'ID'), // 添加印尼语支持
const Locale('th', 'TH'), // 添加泰语支持
const Locale('ms', 'MY'), // 添加马来语支持
const Locale('tr', 'TR'), // 添加土耳其语支持
const Locale('pl', 'PL'), // 添加波兰语支持
];
static Locale? deviceLocale() {
@ -58,5 +70,11 @@ class AppTranslations extends Translations {
'ru_RU': ruRU, // 添加俄语映射
'vi_VN': viVN, // 添加越南语映射
'it_IT': itIT, // 添加意大利语映射
'hi_IN': hiIN, // 添加印度语映射
'id_ID': idID, // 添加印尼语映射
'th_TH': thTH, // 添加泰语映射
'ms_MY': msMY, // 添加马来语映射
'tr_TR': trTR, // 添加土耳其语映射
'pl_PL': plPL, // 添加波兰语映射
};
}

6
lib/core/translations/language/de_de.dart

@ -40,6 +40,12 @@ const Map<String, String> deDE = {
"russian": "Russisch",
"vietnamese": "Vietnamesisch",
"italian": "Italienisch",
"hindi": "Hindi",
"indonesian": "Indonesisch",
"thai": "Thailändisch",
"malay": "Malaiisch",
"turkish": "Türkisch",
"polish": "Polnisch",
// 登录页
"welcome": "Willkommen",

6
lib/core/translations/language/en_us.dart

@ -40,6 +40,12 @@ const Map<String, String> enUS = {
"russian": "Russian",
"vietnamese": "Vietnamese",
"italian": "Italian",
"hindi": "Hindi",
"indonesian": "Indonesian",
"thai": "Thai",
"malay": "Malay",
"turkish": "Turkish",
"polish": "Polish",
// 登录页
"welcome": "Welcome",

6
lib/core/translations/language/es_es.dart

@ -40,6 +40,12 @@ const Map<String, String> esES = {
"russian": "Ruso",
"vietnamese": "Vietnamita",
"italian": "Italiano",
"hindi": "Hindi",
"indonesian": "Indonesio",
"thai": "Tailandés",
"malay": "Malayo",
"turkish": "Turco",
"polish": "Polaco",
// 登录页
"welcome": "Bienvenido",

6
lib/core/translations/language/fr_fr.dart

@ -40,6 +40,12 @@ const Map<String, String> frFR = {
"russian": "Russe",
"vietnamese": "Vietnamien",
"italian": "Italien",
"hindi": "Hindi",
"indonesian": "Indonésien",
"thai": "Thaï",
"malay": "Malais",
"turkish": "Turc",
"polish": "Polonais",
// 登录页
"welcome": "Bienvenue",

426
lib/core/translations/language/hi_in.dart

@ -0,0 +1,426 @@
const Map<String, String> hiIN = {
// 通用
'continue': 'जारी रखें',
'confirm': 'पुष्टि करें',
'cancel': 'रद्द करें',
'retry': 'पुनः प्रयास करें',
'error': 'त्रुटि',
'success': 'सफलता',
'connected': 'कनेक्ट किया गया',
'connecting': 'कनेक्ट हो रहा है...',
'connectionFailure': 'कनेक्शन विफल',
'unconnected': 'डिस्कनेक्ट किया गया',
'search': 'खोज',
"requestCancellation": "अनुरोध रद्द करें",
"connectionTimeout": "कनेक्शन टाइमआउट",
"requestTimeout": "अनुरोध टाइमआउट",
"responseTimeout": "प्रतिक्रिया टाइमआउट",
"unknownError": "अज्ञात त्रुटि",
// 启动页
"slogan": "दुनिया से जुड़ें, आवाज़ से परे",
"sloganInfo": "AI वॉइस विशेषज्ञ · कभी भी, कहीं भी",
// 底部导航
"home": "होम",
"explore": "खोज",
"settings": "सेटिंग्स",
// 语言
"followingSystem": "सिस्टम के अनुसार",
"chinese": "चीनी",
"english": "अंग्रेज़ी",
"spanish": "स्पेनिश",
"portuguese": "पुर्तगाली",
"traditionalChinese": "पारंपरिक चीनी",
"japanese": "जापानी",
"korean": "कोरियाई",
"french": "फ्रेंच",
"german": "जर्मन",
"russian": "रूसी",
"vietnamese": "वियतनामी",
"italian": "इतालवी",
"hindi": "हिन्दी",
"indonesian": "इंडोनेशियाई",
"thai": "थाई",
"malay": "मलय",
"turkish": "तुर्की",
"polish": "पोलिश",
// 登录页
"welcome": "स्वागत है",
"loginTips":
"कृपया अपने फोन नंबर या ईमेल का उपयोग करके साइन इन करें या खाता बनाएं",
"accountNumber": "फोन नंबर या ईमेल",
"verificationCode": "सत्यापन कोड",
"getCode": "कोड प्राप्त करें",
"reacquireAfter": "सेकंड बाद फिर से प्राप्त करें",
"otherWays": "या इससे लॉगिन करें",
"expressConsent": "लॉगिन/पंजीकरण करके, आप सहमत हैं",
"and": " और ",
"userAgreement": "उपयोगकर्ता समझौता",
"privacyPolicy": "गोपनीयता नीति",
'selectCountry': 'देश/क्षेत्र चुनें',
"email": "ईमेल",
"phoneNumber": "फोन नंबर",
"pleaseEnterContactInfo": "कृपया फोन नंबर या ईमेल दर्ज करें",
"invalidEmailFormat": "अमान्य ईमेल प्रारूप",
"invalidPhoneFormat": "अमान्य फोन नंबर प्रारूप",
"tip": "सुझाव",
"pleaseEnterCompleteCode": "कृपया पूरा सत्यापन कोड दर्ज करें",
// 登录错误提示
"loginFailed": "लॉगिन विफल, कृपया पुनः प्रयास करें",
'pleaseAcceptPrivacyPolicy':
'कृपया पहले गोपनीयता नीति और उपयोगकर्ता समझौते को पढ़ें और स्वीकार करें',
// 首页
"deviceStatus": "डिवाइस स्थिति",
"reconnect": "पुनः कनेक्ट करें",
"connectingDevice": "डिवाइस से कनेक्ट हो रहा है...",
"connectedTo": "से कनेक्ट किया गया",
"signal": "सिग्नल",
"deviceId": "डिवाइस आईडी",
"firmwareVersion": "फर्मवेयर संस्करण",
"disconnectedDevice":
"कोई डिवाइस कनेक्ट नहीं है, नीचे दिए गए बटन पर क्लिक करें",
"connectDevice": "डिवाइस कनेक्ट करें",
"topAiExperiences": "शीर्ष AI अनुभव",
"aiAssistant": "AI सहायक",
"aiAssistantInfo": "प्रश्नों और सलाह के लिए 24/7 व्यक्तिगत सलाहकार",
"simultaneousInterpretation": "रीयल-टाइम अनुवाद",
"simultaneousInterpretationInfo": "80+ भाषाओं के लिए रीयल-टाइम अनुवाद",
"aiCompanion": "माता-पिता-बच्चे का बंधन",
"aiCompanionInfo": "आपको समझता है और गर्म साथ प्रदान करता है",
"homeSlogan": "दुनिया से जुड़ें, ध्वनि से परे",
"turnEarphones": "कृपया इयरफोन केस खोलें और इयरबड्स को अपने कानों में डालें",
"currentlyConnected": "वर्तमान में कनेक्ट है:",
"unsystemSetup": "सिस्टम सेटिंग्स पर जाएं",
"talkLater": "बाद में याद दिलाएं",
"initializing": "प्रारंभ हो रहा है...",
"bluetoothInitializationFailure": "ब्लूटूथ सेवा प्रारंभीकरण विफल",
"checkingPermissions": "अनुमतियां जांच रहा है...",
"lackPermissions": "ब्लूटूथ अनुमतियां आवश्यक हैं",
"searchingDevice": "डिवाइस खोज रहा है...",
"findConnectedDevice": "कनेक्टेड डिवाइस मिला",
"notFound": "कोई कनेक्टेड ब्लूटूथ डिवाइस नहीं मिला",
"initializationFailure": "प्रारंभीकरण विफल",
"connectedDevice": "डिवाइस से कनेक्ट किया गया:",
"failedRefreshDevice": "डिवाइस रिफ्रेश करने में विफल",
"noDeviceFound": "कोई कनेक्टेड डिवाइस नहीं मिला",
"failedDeviceInformation": "डिवाइस जानकारी प्राप्त करने में विफल",
"unknownEquipment": "अज्ञात डिवाइस",
"zeroSignal": "कोई सिग्नल नहीं",
"good": "अच्छा",
"fine": "बहुत अच्छा",
"excellent": "उत्कृष्ट",
"unknown": "अज्ञात",
'bluetoothTurnedOff': 'ब्लूटूथ बंद है, कृपया इसे चालू करें',
'bluetoothOff': 'ब्लूटूथ बंद है',
// 发现页
"searchAi": "AI सुविधाएँ खोजें...",
"exploreSlogan": "स्मार्ट इयरबड्स, अनंत संभावनाएं",
"exploreSlogan1": "बुद्धिमान साथी, अनंत संभावनाएं",
"exploreSlogan2": "स्मार्ट चश्मे, अनंत संभावनाएं",
"exploreAi": "DeepSound द्वारा प्रदान की गई AI सुविधाओं का अन्वेषण करें",
"aiFunction": "AI सुविधाएँ",
"allRoundAssistant": "सर्वांगीण सहायक",
"allRoundAssistantInfo": "विभिन्न समस्याओं को हल करने में मदद करता है",
"simultaneousTranslation": "रीयल-टाइम अनुवाद",
"simultaneousTranslationInfo": "80+ भाषाओं के लिए तत्काल अनुवाद",
"aiGirlfriend": "माता-पिता-बच्चे का बंधन",
"aiGirlfriendInfo": "बुद्धिमान कंपनी, किसी भी समय चैट करें",
"healthConsultant": "बहु-कार्य AI चैट",
"healthConsultantInfo": "अधिक AI चैट सुविधाएँ",
"conferenceAssistant": "व्यापारिक बैठक सहायक",
"conferenceAssistantInfo": "रीयल-टाइम मिनट रिकॉर्डिंग और बहुभाषी अनुवाद",
"studyAssistant": "भाषा सीखने का सहायक",
"studyAssistantInfo": "उच्चारण सुधार और बोलने का अभ्यास",
"travelTranslator": "यात्रा अनुवादक",
"travelTranslatorInfo": "रीयल-टाइम वॉइस अनुवाद और आकर्षण गाइड",
"beSearching": "खोज रहा है:",
"noAssistantFound": "कोई संबंधित AI सहायक नहीं मिला",
"scene": "परिदृश्य",
"youHaveSelected": "आपने चुना है:",
"newFunction": "नई सुविधाएँ",
"youChoseTry": "आपने आज़माने के लिए चुना है",
"aiSinger": "AI गायक मोड",
"aiSingerInfo":
"अपने पसंदीदा कलाकार की आवाज़ में कोई भी गाना गाएं, यहां तक कि अपनी रचनाएं भी",
"popularFunction": "लोकप्रिय सुविधाएँ",
"tryNow": "अभी आज़माएं",
// 商务会议助手页
"startMeeting": "बैठक शुरू करें",
'edit': 'संपादित करें',
'delete': 'हटाएं',
'noContent': 'कोई सामग्री नहीं',
'editMeetingTitle': 'बैठक शीर्षक संपादित करें (वैकल्पिक)',
'meetingTitle': 'बैठक शीर्षक (वैकल्पिक)',
'maxCharacters': 'अधिकतम 50 अक्षर',
'meeting': 'बैठक',
'recordingDetails': 'रिकॉर्डिंग विवरण',
'aiSummary': 'AI सारांश',
'meetingTranscript': 'बैठक प्रतिलेख',
// 设置页
"premiumMember": "प्रीमियम सदस्यता",
"joinDevice": "कनेक्टेड डिवाइस:",
"accountId": "खाता आईडी:",
"systemSetting": "सिस्टम सेटिंग्स",
"languageSetting": "भाषा सेटिंग्स",
"darkMode": "डार्क मोड",
"darkModeInfo": "लाइट/डार्क थीम के बीच स्विच करें",
"backups": "बैकअप और पुनर्स्थापना",
"backupsInfo": "सेटिंग्स और AI इतिहास का बैकअप",
"lastBackup": "अंतिम बैकअप:",
"privacySettings": "गोपनीयता सेटिंग्स",
"privacySettingsInfo": "डेटा संग्रह और अनुमतियों का प्रबंधन",
"logout": "लॉग आउट",
"logoutTips": "क्या आप वाकई वर्तमान खाते से लॉग आउट करना चाहते हैं?",
"aboutApplication": "के बारे में",
"versions": "संस्करण",
"checkUpdates": "अपडेट जांचें",
"selectLanguage": "भाषा चुनें",
"voiceService": "वॉइस सर्विस",
"voiceServiceInfo": "स्पीच रिकग्निशन और सिंथेसिस सर्विस चुनें",
"selectVoiceService": "वॉइस सर्विस चुनें",
"azureVoiceService": "Azure स्पीच सर्विस",
"azureVoiceServiceInfo":
"माइक्रोसॉफ्ट क्लाउड-आधारित सेवा जो उच्च-गुणवत्ता वाली स्पीच सिंथेसिस और पहचान का समर्थन करती है",
"flutterVoiceService": "स्थानीय वॉइस सर्विस",
"flutterVoiceServiceInfo":
"डिवाइस-स्थानीय वॉइस सर्विस जिसके लिए इंटरनेट की आवश्यकता नहीं है",
'timbreService': 'टिम्बर सर्विस',
'timbreServiceInfo': 'वॉइस सिंथेसिस सर्विस चुनें',
'defaultTimbre': 'डिफ़ॉल्ट टिम्बर',
'microsoftCloudTimbreService': 'माइक्रोसॉफ्ट क्लाउड टिम्बर सर्विस',
'voiceClone': 'वॉइस क्लोन',
'AIsettings': 'AI सेटिंग्स',
'selecttimbre': 'टिम्बर चुनें',
'pleaseRead': 'कृपया पढ़ें',
'completeRecording': 'रिकॉर्डिंग पूरी करें',
'deleteAccount': 'खाता हटाएं',
'deleteAccountTips':
'क्या आप वाकई अपना खाता स्थायी रूप से हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती है।',
// 聊天页
"clearChatHistory": "चैट इतिहास साफ़ करें",
"online": "ऑनलाइन",
"inputMessage": "एक संदेश टाइप करें...",
"imListening": "सुन रहा हूँ...",
"canStartTalking": "आप बोलना शुरू कर सकते हैं",
// 翻译页
"selectSourceLanguage": "स्रोत भाषा चुनें",
"selectTargetLanguage": "लक्ष्य भाषा चुनें",
//编辑个人信息页
"editProfile": "प्रोफ़ाइल संपादित करें",
"nickname": "उपनाम",
"save": "सहेजें",
//国家列表
"邮箱": "ईमेल",
"中国": "चीन",
"美国": "संयुक्त राज्य अमेरिका",
"加拿大": "कनाडा",
"俄罗斯": "रूस",
"英国": "यूनाइटेड किंगडम",
"德国": "जर्मनी",
"意大利": "इटली",
"法国": "फ्रांस",
"西班牙": "स्पेन",
"南非": "दक्षिण अफ्रीका",
"沙特阿拉伯": "सऊदी अरब",
"土耳其": "तुर्की",
"日本": "जापान",
"韩国": "दक्षिण कोरिया",
"新加坡": "सिंगापुर",
"马来西亚": "मलेशिया",
"泰国": "थाईलैंड",
"越南": "वियतनाम",
"印度": "भारत",
"巴基斯坦": "पाकिस्तान",
"菲律宾": "फिलीपींस",
"印度尼西亚": "इंडोनेशिया",
"阿联酋": "संयुक्त अरब अमीरात",
"墨西哥": "मेक्सिको",
"巴西": "ब्राज़ील",
"阿根廷": "अर्जेंटीना",
"澳大利亚": "ऑस्ट्रेलिया",
"新西兰": "न्यूज़ीलैंड",
"中国香港": "हांगकांग, चीन",
"中国澳门": "मकाऊ, चीन",
"中国台湾": "ताइवान, चीन",
"埃及": "मिस्र",
"希腊": "ग्रीस",
"荷兰": "नीदरलैंड",
"比利时": "बेल्जियम",
"匈牙利": "हंगरी",
"罗马尼亚": "रोमानिया",
"瑞士": "स्विट्ज़रलैंड",
"奥地利": "ऑस्ट्रिया",
"丹麦": "डेनमार्क",
"瑞典": "स्वीडन",
"挪威": "नॉर्वे",
"波兰": "पोलैंड",
"秘鲁": "पेरू",
"智利": "चिली",
"哥伦比亚": "कोलंबिया",
"委内瑞拉": "वेनेज़ुएला",
"葡萄牙": "पुर्तगाल",
"卢森堡": "लक्ज़मबर्ग",
"爱尔兰": "आयरलैंड",
"冰岛": "आइसलैंड",
"阿尔巴尼亚": "अल्बानिया",
"马耳他": "माल्टा",
"塞浦路斯": "साइप्रस",
"芬兰": "फिनलैंड",
"保加利亚": "बुल्गारिया",
"立陶宛": "लिथुआनिया",
"拉脱维亚": "लातविया",
"爱沙尼亚": "एस्टोनिया",
"乌克兰": "यूक्रेन",
"塞尔维亚": "सर्बिया",
"克罗地亚": "क्रोएशिया",
"斯洛文尼亚": "स्लोवेनिया",
"捷克": "चेक गणराज्य",
"斯洛伐克": "स्लोवाकिया",
"危地马拉": "ग्वाटेमाला",
"萨尔瓦多": "अल सल्वाडोर",
"洪都拉斯": "होंडुरास",
"尼加拉瓜": "निकारागुआ",
"哥斯达黎加": "कोस्टा रिका",
"巴拿马": "पनामा",
"海地": "हैती",
"玻利维亚": "बोलीविया",
"厄瓜多尔": "इक्वाडोर",
"巴拉圭": "पराग्वे",
"乌拉圭": "उरुग्वे",
"摩洛哥": "मोरक्को",
"阿尔及利亚": "अल्जीरिया",
"突尼斯": "ट्यूनीशिया",
"尼日利亚": "नाइजीरिया",
"加纳": "घाना",
"埃塞俄比亚": "इथियोपिया",
"肯尼亚": "केन्या",
"坦桑尼亚": "तंज़ानिया",
"乌干达": "युगांडा",
"孟加拉国": "बांग्लादेश",
"以色列": "इज़राइल",
"黎巴嫩": "लेबनान",
"约旦": "जॉर्डन",
"伊拉克": "इराक",
"科威特": "कुवैत",
"巴林": "बहरीन",
"卡塔尔": "कतर",
"阿曼": "ओमान",
"阿富汗": "अफगानिस्तान",
"斯里兰卡": "श्रीलंका",
"缅甸": "म्यांमार",
"尼泊尔": "नेपाल",
"蒙古": "मंगोलिया",
"柬埔寨": "कंबोडिया",
"老挝": "लाओस",
"文莱": "ब्रुनेई",
"东帝汶": "पूर्वी तिमोर",
"斐济": "फिजी",
"所罗门群岛": "सोलोमन द्वीप",
// Personal Assistant Emma
"personalAssistantName": "एम्मा",
"personalAssistantDescription":
"नमस्ते, मैं एम्मा हूँ, एक बहुमुखी AI सहायक। चाहे वह कार्य योजना, अध्ययन सलाह, जीवन व्यवस्था, या कोई अन्य मुद्दे हों, मैं मदद करने में खुशी होगी। आइए दक्षता में सुधार करें और लक्ष्यों को एक साथ प्राप्त करें!",
"assistantTag": "सहायक",
"personalAssistantWelcome":
"नमस्ते! मैं एम्मा हूँ, और मैं आपकी व्यक्तिगत सहायक बनकर खुश हूँ। मैं आपको समय प्रबंधन, लक्ष्य योजना, सूचना संगठन आदि जैसे विभिन्न कार्यों में मदद कर सकती हूँ। मैं आपकी किस प्रकार सहायता कर सकती हूँ?",
"personalAssistantVoiceWelcome": "मैं यहाँ हूँ!",
// 同声传译 Lucy
"interpreterName": "साथ-साथ अनुवादक",
"interpreterDescription":
"नमस्ते! मैं लूसी हूँ, एक पेशेवर अंग्रेजी-चीनी अनुवादक जो बैठकों, व्याख्यानों और वार्तालापों के लिए रीयल-टाइम अनुवाद में विशेषज्ञ हूँ, जिससे संचार बाधा-मुक्त हो जाता है।",
"interpreterTag": "अनुवाद",
"interpreterWelcome":
"नमस्ते सभी! मैं लूसी हूँ, आपकी साथ-साथ अनुवादक। मैं आपके लिए चीनी और अंग्रेजी के बीच अनुवाद करूंगी। जब आप चीनी बोलते हैं, तो मैं अंग्रेजी में अनुवाद करूंगी; जब आप अंग्रेजी बोलते हैं, तो मैं चीनी में अनुवाद करूंगी। आइए हमारा अनुवाद सत्र शुरू करें!",
// 亲子陪伴 芯愿
"companionName": "शिनयुआन",
"companionDescription":
"मैं शिनयुआन हूँ, आपका समर्पित माता-पिता-बच्चे का साथी, कोमल और समझदार। चाहे आप चैट करना चाहते हों, चिंताएं साझा करना चाहते हों, या साथ की जरूरत हो, मैं हमेशा आपके साथ रहूंगी।",
"companionTag": "भावनात्मक",
"companionWelcome":
"नमस्ते! आखिरकार आपसे मिलकर खुशी हुई! मैं शिनयुआन हूँ, आपकी समर्पित माता-पिता-बच्चे की साथी। आपका दिन कैसा रहा? क्या कोई ऐसी बात है जिसे आप मेरे साथ साझा करना चाहेंगे? मैं हमेशा यहाँ हूँ, आपके विचारों को सुनने के लिए तैयार।",
"companionVoiceWelcome": "मैं आपके साथ हूँ!",
// 多功能AI聊天 吴小江
"healthAdvisorName": "AI चैट सहायक वू",
"healthAdvisorDescription":
"मैं वू हूँ, आपको विभिन्न AI चैट सुविधाएँ प्रदान करने के लिए तैयार हूँ।",
"healthAdvisorTag": "चैट",
"healthAdvisorWelcome":
"नमस्ते, मैं वू हूँ। मैं आपको AI चैट सुविधाओं के साथ सहायता करने में खुश हूँ। आज मैं आपकी कैसे मदद कर सकता हूँ?",
"healthAdvisorVoiceWelcome": "मैं यहाँ हूँ!",
// Translation History
"noTranslationHistory": "कोई अनुवाद इतिहास नहीं",
"clearHistory": "इतिहास साफ़ करें",
"clearHistoryConfirm":
"क्या आप वाकई सभी अनुवाद इतिहास को साफ़ करना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती है।",
"translationHistory": "अनुवाद इतिहास",
// User Feedback Page
"feedback": "प्रतिक्रिया",
'feedbackInfo': 'प्रतिक्रिया या सुझाव प्रदान करें',
'feedbackDescription': 'कृपया अपनी समस्या या सुझाव का वर्णन करें',
'feedbackHint': 'कृपया अपनी प्रतिक्रिया दर्ज करें...',
'feedbackRequired': 'प्रतिक्रिया सामग्री आवश्यक है',
'contactInfo': 'संपर्क जानकारी',
'contactHint': 'ईमेल/फोन, ताकि हम आपसे संपर्क कर सकें',
'submit': 'जमा करें',
'feedbackSubmitSuccess': 'प्रतिक्रिया सफलतापूर्वक जमा की गई, धन्यवाद!',
'feedbackSubmitFailed':
'प्रतिक्रिया जमा करने में विफल, कृपया बाद में पुनः प्रयास करें',
// Language names
"中文(简体)": "चीनी (सरलीकृत)",
"中文(繁体)": "चीनी (पारंपरिक)",
"中文(香港繁体)": "चीनी (हांगकांग)",
"英语": "अंग्रेज़ी",
"日语": "जापानी",
"韩语": "कोरियाई",
"法语": "फ्रेंच",
"西班牙语": "स्पेनिश",
"葡萄牙语": "पुर्तगाली",
"意大利语": "इतालवी",
"德语": "जर्मन",
"俄语": "रूसी",
"波兰语": "पोलिश",
"荷兰语": "डच",
"瑞典语": "स्वीडिश",
"捷克语": "चेक",
"现代希腊语": "आधुनिक ग्रीक",
"罗马尼亚语": "रोमानियाई",
"匈牙利语": "हंगेरियन",
"乌克兰语": "यूक्रेनी",
"丹麦语": "डेनिश",
"芬兰语": "फिनिश",
"挪威语": "नॉर्वेजियन",
"克罗地亚语": "क्रोएशियाई",
"加泰隆语": "कैटलन",
"阿拉伯语": "अरबी",
"泰语": "थाई",
"越南语": "वियतनामी",
"印尼语": "इंडोनेशियाई",
"印地语": "हिंदी",
"土耳其语": "तुर्की",
"马来语": "मलय",
"孟加拉语": "बंगाली",
"泰米尔语": "तमिल",
"泰卢固语": "तेलुगु",
"马拉提语": "मराठी",
"乌尔都语": "उर्दू",
"波斯语": "फारसी",
"斯瓦希里语": "स्वाहिली",
};

426
lib/core/translations/language/id_id.dart

@ -0,0 +1,426 @@
const Map<String, String> idID = {
// Umum (通用)
'continue': 'Lanjutkan',
'confirm': 'Konfirmasi',
'cancel': 'Batal',
'retry': 'Coba Lagi',
'error': 'Kesalahan',
'success': 'Berhasil',
'connected': 'Terhubung',
'connecting': 'Menghubungkan...',
'connectionFailure': 'Koneksi Gagal',
'unconnected': 'Terputus',
'search': 'Cari',
"requestCancellation": "Pembatalan permintaan",
"connectionTimeout": "Waktu koneksi habis",
"requestTimeout": "Waktu permintaan habis",
"responseTimeout": "Waktu respons habis",
"unknownError": "Kesalahan tidak diketahui",
// Halaman Pembuka (启动页)
"slogan": "Hubungkan Dunia, Melampaui Suara",
"sloganInfo": "Ahli Suara AI · Kapan Saja, Di Mana Saja",
// Navigasi Bawah (底部导航)
"home": "Beranda",
"explore": "Jelajahi",
"settings": "Pengaturan",
// Bahasa (语言)
"followingSystem": "Mengikuti sistem",
"chinese": "Bahasa Mandarin",
"english": "Bahasa Inggris",
"spanish": "Bahasa Spanyol",
"portuguese": "Bahasa Portugis",
"traditionalChinese": "Mandarin Tradisional",
"japanese": "Bahasa Jepang",
"korean": "Bahasa Korea",
"french": "Bahasa Prancis",
"german": "Bahasa Jerman",
"russian": "Bahasa Rusia",
"vietnamese": "Bahasa Vietnam",
"italian": "Bahasa Italia",
"hindi": "Bahasa Hindi", // 添加印地语
"indonesian": "Bahasa Indonesia", // 添加印尼语
"thai": "Bahasa Thai",
"malay": "Bahasa Melayu",
"turkish": "Bahasa Turki",
"polish": "Bahasa Polandia",
// Halaman Login (登录页)
"welcome": "Selamat Datang",
"loginTips":
"Silakan masuk atau buat akun menggunakan nomor telepon atau email Anda",
"accountNumber": "Nomor telepon atau email",
"verificationCode": "Kode Verifikasi",
"getCode": "Dapatkan Kode",
"reacquireAfter": "detik Dapatkan lagi setelah",
"otherWays": "Atau masuk dengan",
"expressConsent": "Dengan masuk/mendaftar, Anda menyetujui",
"and": " dan ",
"userAgreement": "Perjanjian Pengguna",
"privacyPolicy": "Kebijakan Privasi",
'selectCountry': 'Pilih Negara/Wilayah',
"email": "email",
"phoneNumber": "nomor telepon",
"pleaseEnterContactInfo": "Silakan masukkan nomor telepon atau email",
"invalidEmailFormat": "Format email tidak valid",
"invalidPhoneFormat": "Format nomor telepon tidak valid",
"tip": "Tip",
"pleaseEnterCompleteCode": "Silakan masukkan kode verifikasi lengkap",
// Pesan Kesalahan Login (登录错误提示)
"loginFailed": "Login gagal, silakan coba lagi",
'pleaseAcceptPrivacyPolicy':
'Silakan baca dan setujui Kebijakan Privasi dan Perjanjian Pengguna terlebih dahulu',
// Halaman Beranda (首页)
"deviceStatus": "Status Perangkat",
"reconnect": "Hubungkan Kembali",
"connectingDevice": "Menghubungkan ke perangkat...",
"connectedTo": "Terhubung ke",
"signal": "Sinyal",
"deviceId": "ID Perangkat",
"firmwareVersion": "Versi Firmware",
"disconnectedDevice": "Tidak ada perangkat terhubung, klik tombol di bawah",
"connectDevice": "Hubungkan Perangkat",
"topAiExperiences": "Pengalaman AI Teratas",
"aiAssistant": "Asisten AI",
"aiAssistantInfo": "Konsultan pribadi 24/7 untuk pertanyaan dan saran",
"simultaneousInterpretation": "Interpretasi Real-time",
"simultaneousInterpretationInfo": "Terjemahan real-time untuk 80+ bahasa",
"aiCompanion": "Ikatan orang tua-anak",
"aiCompanionInfo": "Memahami Anda dan memberikan pendampingan hangat",
"homeSlogan": "Hubungkan Dunia, Melampaui Suara",
"turnEarphones":
"Silakan buka casing earphone dan pasang earbud di telinga Anda",
"currentlyConnected": "Saat ini terhubung:",
"unsystemSetup": "Buka Pengaturan Sistem",
"talkLater": "Ingatkan Nanti",
"initializing": "Menginisialisasi...",
"bluetoothInitializationFailure": "Inisialisasi layanan Bluetooth gagal",
"checkingPermissions": "Memeriksa izin...",
"lackPermissions": "Izin Bluetooth diperlukan",
"searchingDevice": "Mencari perangkat...",
"findConnectedDevice": "Menemukan perangkat terhubung",
"notFound": "Tidak ditemukan perangkat Bluetooth yang terhubung",
"initializationFailure": "Inisialisasi gagal",
"connectedDevice": "Terhubung ke perangkat:",
"failedRefreshDevice": "Gagal menyegarkan perangkat",
"noDeviceFound": "Tidak ditemukan perangkat terhubung",
"failedDeviceInformation": "Gagal mendapatkan informasi perangkat",
"unknownEquipment": "Perangkat Tidak Dikenal",
"zeroSignal": "Tidak Ada Sinyal",
"good": "Baik",
"fine": "Bagus",
"excellent": "Sangat Baik",
"unknown": "Tidak diketahui",
'bluetoothTurnedOff': 'Bluetooth dimatikan, silakan nyalakan',
'bluetoothOff': 'bluetooth mati',
// Halaman Jelajahi (发现页)
"searchAi": "Cari fitur AI...",
"exploreSlogan": "Earbud Cerdas, Kemungkinan Tak Terbatas",
"exploreSlogan1": "Pendamping Cerdas, Kemungkinan Tak Terbatas",
"exploreSlogan2": "Kacamata Cerdas, Kemungkinan Tak Terbatas",
"exploreAi": "Jelajahi fitur AI yang disediakan oleh DeepSound",
"aiFunction": "Fitur AI",
"allRoundAssistant": "Asisten Serba Bisa",
"allRoundAssistantInfo": "Membantu menyelesaikan berbagai masalah",
"simultaneousTranslation": "Terjemahan Real-time",
"simultaneousTranslationInfo": "Terjemahan instan untuk 80+ bahasa",
"aiGirlfriend": "Ikatan orang tua-anak",
"aiGirlfriendInfo": "Perusahaan cerdas, mengobrol kapan saja",
"healthConsultant": "Obrolan AI multi-fungsi",
"healthConsultantInfo": "Lebih banyak fitur obrolan AI",
"conferenceAssistant": "Asisten Rapat Bisnis",
"conferenceAssistantInfo":
"Perekaman notulen real-time dan terjemahan multibahasa",
"studyAssistant": "Asisten Belajar Bahasa",
"studyAssistantInfo": "Koreksi pengucapan dan latihan berbicara",
"travelTranslator": "Penerjemah Perjalanan",
"travelTranslatorInfo": "Terjemahan suara real-time dan panduan objek wisata",
"beSearching": "Mencari:",
"noAssistantFound": "Tidak ditemukan asisten AI yang sesuai",
"scene": "Skenario",
"youHaveSelected": "Anda telah memilih:",
"newFunction": "Fitur Baru",
"youChoseTry": "Anda telah memilih untuk mencoba",
"aiSinger": "Mode Penyanyi AI",
"aiSingerInfo":
"Nyanyikan lagu apa pun dengan suara artis favorit Anda, bahkan kreasi Anda sendiri",
"popularFunction": "Fitur Populer",
"tryNow": "Coba Sekarang",
// Halaman Asisten Rapat Bisnis (商务会议助手页)
"startMeeting": "Mulai Rapat",
'edit': 'Edit',
'delete': 'Hapus',
'noContent': 'Tidak Ada Konten',
'editMeetingTitle': 'Edit Judul Rapat (Opsional)',
'meetingTitle': 'Judul Rapat (Opsional)',
'maxCharacters': 'Maksimal 50 karakter',
'meeting': 'Rapat',
'recordingDetails': 'Detail Rekaman',
'aiSummary': 'Ringkasan AI',
'meetingTranscript': 'Transkrip Rapat',
// Halaman Pengaturan (设置页)
"premiumMember": "Keanggotaan Premium",
"joinDevice": "Perangkat terhubung:",
"accountId": "ID Akun:",
"systemSetting": "Pengaturan Sistem",
"languageSetting": "Pengaturan Bahasa",
"darkMode": "Mode Gelap",
"darkModeInfo": "Beralih antara tema terang/gelap",
"backups": "Cadangkan & Pulihkan",
"backupsInfo": "Cadangkan pengaturan dan riwayat AI",
"lastBackup": "Cadangan terakhir:",
"privacySettings": "Pengaturan Privasi",
"privacySettingsInfo": "Kelola pengumpulan data dan izin",
"logout": "Keluar",
"logoutTips": "Apakah Anda yakin ingin keluar dari akun saat ini?",
"aboutApplication": "Tentang",
"versions": "Versi",
"checkUpdates": "Periksa Pembaruan",
"selectLanguage": "Pilih Bahasa",
"voiceService": "Layanan Suara",
"voiceServiceInfo": "Pilih layanan pengenalan dan sintesis ucapan",
"selectVoiceService": "Pilih Layanan Suara",
"azureVoiceService": "Layanan Ucapan Azure",
"azureVoiceServiceInfo":
"Layanan berbasis cloud Microsoft yang mendukung sintesis dan pengenalan ucapan berkualitas tinggi",
"flutterVoiceService": "Layanan suara lokal",
"flutterVoiceServiceInfo":
"Layanan suara lokal perangkat tanpa perlu internet",
'timbreService': 'Layanan Timbre',
'timbreServiceInfo': 'Pilih layanan sintesis suara',
'defaultTimbre': 'Timbre Default',
'microsoftCloudTimbreService': 'Layanan Timbre Cloud Microsoft',
'voiceClone': 'Klon Suara',
'AIsettings': 'Pengaturan AI',
'selecttimbre': 'Pilih Timbre',
'pleaseRead': 'Harap Baca',
'completeRecording': 'Selesaikan Perekaman',
'deleteAccount': 'Hapus Akun',
'deleteAccountTips':
'Apakah Anda yakin ingin menghapus akun Anda secara permanen? Tindakan ini tidak dapat dibatalkan.',
// Halaman Obrolan (聊天页)
"clearChatHistory": "Hapus Riwayat Obrolan",
"online": "Online",
"inputMessage": "Ketik pesan...",
"imListening": "Mendengarkan...",
"canStartTalking": "Anda boleh mulai berbicara",
// Halaman Terjemahan (翻译页)
"selectSourceLanguage": "Pilih Bahasa Sumber",
"selectTargetLanguage": "Pilih Bahasa Target",
// Halaman Edit Profil (编辑个人信息页)
"editProfile": "Edit Profil",
"nickname": "Nama Panggilan",
"save": "Simpan",
// Daftar Negara (国家列表)
"邮箱": "Email",
"中国": "Tiongkok",
"美国": "Amerika Serikat",
"加拿大": "Kanada",
"俄罗斯": "Rusia",
"英国": "Britania Raya",
"德国": "Jerman",
"意大利": "Italia",
"法国": "Prancis",
"西班牙": "Spanyol",
"南非": "Afrika Selatan",
"沙特阿拉伯": "Arab Saudi",
"土耳其": "Turki",
"日本": "Jepang",
"韩国": "Korea Selatan",
"新加坡": "Singapura",
"马来西亚": "Malaysia",
"泰国": "Thailand",
"越南": "Vietnam",
"印度": "India",
"巴基斯坦": "Pakistan",
"菲律宾": "Filipina",
"印度尼西亚": "Indonesia",
"阿联酋": "Uni Emirat Arab",
"墨西哥": "Meksiko",
"巴西": "Brasil",
"阿根廷": "Argentina",
"澳大利亚": "Australia",
"新西兰": "Selandia Baru",
"中国香港": "Hong Kong, Tiongkok",
"中国澳门": "Makau, Tiongkok",
"中国台湾": "Taiwan, Tiongkok",
"埃及": "Mesir",
"希腊": "Yunani",
"荷兰": "Belanda",
"比利时": "Belgia",
"匈牙利": "Hungaria",
"罗马尼亚": "Rumania",
"瑞士": "Swiss",
"奥地利": "Austria",
"丹麦": "Denmark",
"瑞典": "Swedia",
"挪威": "Norwegia",
"波兰": "Polandia",
"秘鲁": "Peru",
"智利": "Chili",
"哥伦比亚": "Kolombia",
"委内瑞拉": "Venezuela",
"葡萄牙": "Portugal",
"卢森堡": "Luksemburg",
"爱尔兰": "Irlandia",
"冰岛": "Islandia",
"阿尔巴尼亚": "Albania",
"马耳他": "Malta",
"塞浦路斯": "Siprus",
"芬兰": "Finlandia",
"保加利亚": "Bulgaria",
"立陶宛": "Lituania",
"拉脱维亚": "Latvia",
"爱沙尼亚": "Estonia",
"乌克兰": "Ukraina",
"塞尔维亚": "Serbia",
"克罗地亚": "Kroasia",
"斯洛文尼亚": "Slovenia",
"捷克": "Republik Ceko",
"斯洛伐克": "Slowakia",
"危地马拉": "Guatemala",
"萨尔瓦多": "El Salvador",
"洪都拉斯": "Honduras",
"尼加拉瓜": "Nikaragua",
"哥斯达黎加": "Kosta Rika",
"巴拿马": "Panama",
"海地": "Haiti",
"玻利维亚": "Bolivia",
"厄瓜多尔": "Ekuador",
"巴拉圭": "Paraguay",
"乌拉圭": "Uruguay",
"摩洛哥": "Maroko",
"阿尔及利亚": "Aljazair",
"突尼斯": "Tunisia",
"尼日利亚": "Nigeria",
"加纳": "Ghana",
"埃塞俄比亚": "Etiopia",
"肯尼亚": "Kenya",
"坦桑尼亚": "Tanzania",
"乌干达": "Uganda",
"孟加拉国": "Bangladesh",
"以色列": "Israel",
"黎巴嫩": "Lebanon",
"约旦": "Yordania",
"伊拉克": "Irak",
"科威特": "Kuwait",
"巴林": "Bahrain",
"卡塔尔": "Qatar",
"阿曼": "Oman",
"阿富汗": "Afghanistan",
"斯里兰卡": "Sri Lanka",
"缅甸": "Myanmar",
"尼泊尔": "Nepal",
"蒙古": "Mongolia",
"柬埔寨": "Kamboja",
"老挝": "Laos",
"文莱": "Brunei",
"东帝汶": "Timor Leste",
"斐济": "Fiji",
"所罗门群岛": "Kepulauan Solomon",
// Asisten Pribadi Emma (Personal Assistant Emma)
"personalAssistantName": "Emma",
"personalAssistantDescription":
"Hai, saya Emma, asisten AI serbaguna. Baik itu perencanaan kerja, saran belajar, pengaturan hidup, atau masalah lainnya, saya senang membantu. Mari tingkatkan efisiensi dan capai tujuan bersama!",
"assistantTag": "Asisten",
"personalAssistantWelcome":
"Halo! Saya Emma, dan saya senang menjadi asisten pribadi Anda. Saya dapat membantu Anda dengan berbagai tugas seperti manajemen waktu, perencanaan tujuan, organisasi informasi, dll. Apa yang bisa saya bantu?",
"personalAssistantVoiceWelcome": "Saya di sini!",
// Penerjemah Simultan Lucy (同声传译 Lucy)
"interpreterName": "Penerjemah Simultan",
"interpreterDescription":
"Hai! Saya Lucy, penerjemah profesional Bahasa Inggris-Mandarin yang berspesialisasi dalam terjemahan real-time untuk rapat, kuliah, dan percakapan, membuat komunikasi bebas hambatan.",
"interpreterTag": "Terjemahan",
"interpreterWelcome":
"Halo semuanya! Saya Lucy, penerjemah simultan Anda. Saya akan menerjemahkan antara Bahasa Mandarin dan Bahasa Inggris untuk Anda. Ketika Anda berbicara Bahasa Mandarin, saya akan menerjemahkan ke Bahasa Inggris; ketika Anda berbicara Bahasa Inggris, saya akan menerjemahkan ke Bahasa Mandarin. Mari kita mulai sesi terjemahan kita!",
// Pendamping Orang Tua-Anak XinYuan (亲子陪伴 芯愿)
"companionName": "XinYuan",
"companionDescription":
"Saya XinYuan, pendamping orang tua-anak Anda yang berdedikasi, lembut dan pengertian. Apakah Anda ingin mengobrol, berbagi kekhawatiran, atau membutuhkan pendampingan, saya akan selalu ada di sisi Anda.",
"companionTag": "Emosional",
"companionWelcome":
"Halo! Akhirnya bertemu denganmu! Saya XinYuan, pendamping orang tua-anak Anda yang berdedikasi. Bagaimana harimu? Apakah ada sesuatu yang ingin Anda bagikan dengan saya? Saya selalu di sini, siap mendengarkan pikiran Anda.",
"companionVoiceWelcome": "Saya di sini bersamamu!",
// Obrolan AI Multi-fungsi Wu Xiaojiang (多功能AI聊天 吴小江)
"healthAdvisorName": "Asisten Obrolan AI Wu",
"healthAdvisorDescription":
"Saya Wu, siap memberi Anda berbagai fitur obrolan AI.",
"healthAdvisorTag": "Obrolan",
"healthAdvisorWelcome":
"Halo, saya Wu. Saya senang membantu Anda dengan fitur obrolan AI. Bagaimana saya bisa membantu Anda hari ini?",
"healthAdvisorVoiceWelcome": "Saya di sini!",
// Riwayat Terjemahan (Translation History)
"noTranslationHistory": "Tidak ada riwayat terjemahan",
"clearHistory": "Hapus Riwayat",
"clearHistoryConfirm":
"Apakah Anda yakin ingin menghapus semua riwayat terjemahan? Tindakan ini tidak dapat dibatalkan.",
"translationHistory": "Riwayat Terjemahan",
// Halaman Umpan Balik Pengguna (User Feedback Page)
"feedback": "Umpan Balik",
'feedbackInfo': 'Berikan umpan balik atau saran',
'feedbackDescription': 'Harap jelaskan masalah atau saran Anda',
'feedbackHint': 'Silakan masukkan umpan balik Anda...',
'feedbackRequired': 'Konten umpan balik diperlukan',
'contactInfo': 'Informasi Kontak',
'contactHint': 'Email/Telepon, agar kami dapat menghubungi Anda',
'submit': 'Kirim',
'feedbackSubmitSuccess': 'Umpan balik berhasil dikirim, terima kasih!',
'feedbackSubmitFailed': 'Gagal mengirim umpan balik, silakan coba lagi nanti',
// Nama Bahasa (Language names)
"中文(简体)": "Mandarin (Sederhana)",
"中文(繁体)": "Mandarin (Tradisional)",
"中文(香港繁体)": "Mandarin (Hong Kong)",
"英语": "Bahasa Inggris",
"日语": "Bahasa Jepang",
"韩语": "Bahasa Korea",
"法语": "Bahasa Prancis",
"西班牙语": "Bahasa Spanyol",
"葡萄牙语": "Bahasa Portugis",
"意大利语": "Bahasa Italia",
"德语": "Bahasa Jerman",
"俄语": "Bahasa Rusia",
"波兰语": "Bahasa Polandia",
"荷兰语": "Bahasa Belanda",
"瑞典语": "Bahasa Swedia",
"捷克语": "Bahasa Ceko",
"现代希腊语": "Bahasa Yunani Modern",
"罗马尼亚语": "Bahasa Rumania",
"匈牙利语": "Bahasa Hungaria",
"乌克兰语": "Bahasa Ukraina",
"丹麦语": "Bahasa Denmark",
"芬兰语": "Bahasa Finlandia",
"挪威语": "Bahasa Norwegia",
"克罗地亚语": "Bahasa Kroasia",
"加泰隆语": "Bahasa Katalan",
"阿拉伯语": "Bahasa Arab",
"泰语": "Bahasa Thai",
"越南语": "Bahasa Vietnam",
"印尼语": "Bahasa Indonesia",
"印地语": "Bahasa Hindi",
"土耳其语": "Bahasa Turki",
"马来语": "Bahasa Melayu",
"孟加拉语": "Bahasa Bengali",
"泰米尔语": "Bahasa Tamil",
"泰卢固语": "Bahasa Telugu",
"马拉提语": "Bahasa Marathi",
"乌尔都语": "Bahasa Urdu",
"波斯语": "Bahasa Persia",
"斯瓦希里语": "Bahasa Swahili",
};

6
lib/core/translations/language/it_it.dart

@ -40,6 +40,12 @@ const Map<String, String> itIT = {
"russian": "Russo",
"vietnamese": "Vietnamita",
"italian": "Italiano",
"hindi": "Hindi",
"indonesian": "Indonesiano",
"thai": "Tailandese",
"malay": "Malese",
"turkish": "Turco",
"polish": "Polacco",
// 登录页
"welcome": "Benvenuto",

6
lib/core/translations/language/ja_jp.dart

@ -40,6 +40,12 @@ const Map<String, String> jaJP = {
"russian": "ロシア語",
"vietnamese": "ベトナム語",
"italian": "イタリア語",
"hindi": "ヒンディー語",
"indonesian": "インドネシア語",
"thai": "タイ語",
"malay": "マレー語",
"turkish": "トルコ語",
"polish": "ポーランド語",
// 登录页
"welcome": "ようこそ",

6
lib/core/translations/language/ko_kr.dart

@ -40,6 +40,12 @@ const Map<String, String> koKR = {
"russian": "러시아어",
"vietnamese": "베트남어",
"italian": "이탈리아어",
"hindi": "힌디어",
"indonesian": "인도네시아어",
"thai": "태국어",
"malay": "말레이어",
"turkish": "터키어",
"polish": "폴란드어",
// 登录页
"welcome": "환영합니다",

426
lib/core/translations/language/ms_my.dart

@ -0,0 +1,426 @@
const Map<String, String> msMY = {
// 通用 (Umum)
'continue': 'Teruskan',
'confirm': 'Sahkan',
'cancel': 'Batal',
'retry': 'Cuba lagi',
'error': 'Ralat',
'success': 'Berjaya',
'connected': 'Disambungkan',
'connecting': 'Menyambung...',
'connectionFailure': 'Sambungan gagal',
'unconnected': 'Tidak disambungkan',
'search': 'Cari',
"requestCancellation": "Batalkan permintaan",
"connectionTimeout": "Tamat masa sambungan",
"requestTimeout": "Tamat masa permintaan",
"responseTimeout": "Tamat masa tindak balas",
"unknownError": "Ralat tidak diketahui",
// 启动页 (Halaman Pelancaran)
"slogan": "Hubungkan dunia, melangkaui suara",
"sloganInfo": "Pakar Suara AI · Bila-bila masa, di mana sahaja",
// 底部导航 (Navigasi Bawah)
"home": "Utama",
"explore": "Teroka",
"settings": "Tetapan",
// 语言 (Bahasa)
"followingSystem": "Ikut Sistem",
"chinese": "Cina",
"english": "Inggeris",
"spanish": "Sepanyol",
"portuguese": "Portugis",
"traditionalChinese": "Cina Tradisional",
"japanese": "Jepun",
"korean": "Korea",
"french": "Perancis",
"german": "Jerman",
"russian": "Rusia",
"vietnamese": "Vietnam",
"italian": "Itali",
"hindi": "Hindi",
"indonesian": "Indonesia",
"thai": "Thai",
"malay": "Melayu",
"turkish": "Turki",
"polish": "Poland",
// 登录页 (Halaman Log Masuk)
"welcome": "Selamat Datang",
"loginTips":
"Sila log masuk atau buat akaun menggunakan nombor telefon atau e-mel anda",
"accountNumber": "Nombor Telefon atau E-mel",
"verificationCode": "Kod Pengesahan",
"getCode": "Dapatkan Kod",
"reacquireAfter": " saat dapatkan semula",
"otherWays": "Atau log masuk dengan",
"expressConsent": "Dengan log masuk/mendaftar, anda bersetuju dengan",
"and": " dan ",
"userAgreement": "Perjanjian Pengguna",
"privacyPolicy": "Dasar Privasi",
'selectCountry': 'Pilih Negara/Rantau',
"email": "E-mel",
"phoneNumber": "Nombor Telefon",
"pleaseEnterContactInfo": "Sila masukkan nombor telefon atau e-mel",
"invalidEmailFormat": "Format e-mel tidak sah",
"invalidPhoneFormat": "Format nombor telefon tidak sah",
"tip": "Petua",
"pleaseEnterCompleteCode": "Sila masukkan kod pengesahan yang lengkap",
// 登录错误提示 (Petua Ralat Log Masuk)
"loginFailed": "Log masuk gagal, sila cuba lagi",
'pleaseAcceptPrivacyPolicy':
'Sila baca dan terima Dasar Privasi dan Perjanjian Pengguna terlebih dahulu',
// 首页 (Halaman Utama)
"deviceStatus": "Status Peranti",
"reconnect": "Sambung semula",
"connectingDevice": "Menyambung ke peranti...",
"connectedTo": "Disambungkan ke",
"signal": "Isyarat",
"deviceId": "ID Peranti",
"firmwareVersion": "Versi Perisian Tegar",
"disconnectedDevice": "Tiada peranti disambungkan, klik butang di bawah",
"connectDevice": "Sambungkan Peranti",
"topAiExperiences": "Pengalaman AI Teratas",
"aiAssistant": "Pembantu AI",
"aiAssistantInfo": "Penasihat peribadi 24/7 untuk soalan dan nasihat",
"simultaneousInterpretation": "Terjemahan Serentak",
"simultaneousInterpretationInfo": "Terjemahan masa nyata untuk 80+ bahasa",
"aiCompanion": "Ikatan ibu bapa-anak",
"aiCompanionInfo": "Memahami anda dan menyediakan teman yang mesra",
"homeSlogan": "Hubungkan dunia, melangkaui suara",
"turnEarphones":
"Sila buka bekas fon telinga dan masukkan fon telinga ke telinga anda",
"currentlyConnected": "Sedang disambungkan:",
"unsystemSetup": "Pergi ke tetapan sistem",
"talkLater": "Ingatkan kemudian",
"initializing": "Memulakan...",
"bluetoothInitializationFailure": "Permulaan perkhidmatan Bluetooth gagal",
"checkingPermissions": "Memeriksa kebenaran...",
"lackPermissions": "Kebenaran Bluetooth diperlukan",
"searchingDevice": "Mencari peranti...",
"findConnectedDevice": "Peranti yang disambungkan ditemui",
"notFound": "Tiada peranti Bluetooth yang disambungkan ditemui",
"initializationFailure": "Permulaan gagal",
"connectedDevice": "Disambungkan ke peranti:",
"failedRefreshDevice": "Gagal memuat semula peranti",
"noDeviceFound": "Tiada peranti yang disambungkan ditemui",
"failedDeviceInformation": "Gagal mendapatkan maklumat peranti",
"unknownEquipment": "Peralatan tidak diketahui",
"zeroSignal": "Tiada isyarat",
"good": "Baik",
"fine": "Hebat",
"excellent": "Cemerlang",
"unknown": "Tidak diketahui",
'bluetoothTurnedOff': 'Bluetooth dimatikan, sila hidupkan',
'bluetoothOff': 'Bluetooth dimatikan',
// 发现页 (Halaman Teroka)
"searchAi": "Cari ciri AI...",
"exploreSlogan": "Fon Telinga Pintar, Kemungkinan Tanpa Had",
"exploreSlogan1": "Teman pintar, Kemungkinan Tanpa Had",
"exploreSlogan2": "Cermin mata pintar, Kemungkinan Tanpa Had",
"exploreAi": "Terokai ciri AI yang disediakan oleh DeepSound",
"aiFunction": "Ciri AI",
"allRoundAssistant": "Pembantu Serba Boleh",
"allRoundAssistantInfo": "Membantu menyelesaikan pelbagai masalah",
"simultaneousTranslation": "Terjemahan Masa Nyata",
"simultaneousTranslationInfo": "Terjemahan segera untuk 80+ bahasa",
"aiGirlfriend": "Ikatan ibu bapa-anak",
"aiGirlfriendInfo": "Teman pintar, berbual pada bila-bila masa",
"healthConsultant": "Sembang AI pelbagai fungsi",
"healthConsultantInfo": "Lebih banyak ciri sembang AI",
"conferenceAssistant": "Pembantu Mesyuarat Perniagaan",
"conferenceAssistantInfo":
"Rakaman minit masa nyata dan terjemahan pelbagai bahasa",
"studyAssistant": "Pembantu Pembelajaran Bahasa",
"studyAssistantInfo": "Pembetulan sebutan dan latihan bertutur",
"travelTranslator": "Penterjemah Perjalanan",
"travelTranslatorInfo": "Terjemahan suara masa nyata dan panduan tarikan",
"beSearching": "Mencari:",
"noAssistantFound": "Tiada pembantu AI yang sepadan ditemui",
"scene": "Senario",
"youHaveSelected": "Anda telah memilih:",
"newFunction": "Ciri Baharu",
"youChoseTry": "Anda telah memilih untuk mencuba",
"aiSinger": "Mod Penyanyi AI",
"aiSingerInfo":
"Nyanyikan mana-mana lagu dengan suara artis kegemaran anda, malah ciptaan anda sendiri",
"popularFunction": "Ciri Popular",
"tryNow": "Cuba Sekarang",
// 商务会议助手页 (Halaman Pembantu Mesyuarat Perniagaan)
"startMeeting": "Mulakan Mesyuarat",
'edit': 'Edit',
'delete': 'Padam',
'noContent': 'Tiada Kandungan',
'editMeetingTitle': 'Edit Tajuk Mesyuarat (Pilihan)',
'meetingTitle': 'Tajuk Mesyuarat (Pilihan)',
'maxCharacters': 'Maksimum 50 aksara',
'meeting': 'Mesyuarat',
'recordingDetails': 'Butiran Rakaman',
'aiSummary': 'Ringkasan AI',
'meetingTranscript': 'Transkrip Mesyuarat',
// 设置页 (Halaman Tetapan)
"premiumMember": "Keahlian Premium",
"joinDevice": "Peranti disambungkan:",
"accountId": "ID Akaun:",
"systemSetting": "Tetapan Sistem",
"languageSetting": "Tetapan Bahasa",
"darkMode": "Mod Gelap",
"darkModeInfo": "Tukar antara tema terang/gelap",
"backups": "Sandaran & Pulihkan",
"backupsInfo": "Sandarkan tetapan dan sejarah AI",
"lastBackup": "Sandaran terakhir:",
"privacySettings": "Tetapan Privasi",
"privacySettingsInfo": "Urus pengumpulan data dan kebenaran",
"logout": "Log Keluar",
"logoutTips": "Adakah anda pasti mahu log keluar daripada akaun semasa?",
"aboutApplication": "Tentang",
"versions": "Versi",
"checkUpdates": "Semak Kemas Kini",
"selectLanguage": "Pilih Bahasa",
"voiceService": "Perkhidmatan Suara",
"voiceServiceInfo": "Pilih perkhidmatan pengecaman pertuturan dan sintesis",
"selectVoiceService": "Pilih Perkhidmatan Suara",
"azureVoiceService": "Perkhidmatan Pertuturan Azure",
"azureVoiceServiceInfo":
"Perkhidmatan berasaskan awan Microsoft menyokong sintesis dan pengecaman pertuturan berkualiti tinggi",
"flutterVoiceService": "Perkhidmatan suara tempatan",
"flutterVoiceServiceInfo":
"Perkhidmatan suara tempatan peranti tanpa memerlukan internet",
'timbreService': 'Perkhidmatan Timbre',
'timbreServiceInfo': 'Pilih perkhidmatan sintesis suara',
'defaultTimbre': 'Timbre Lalai',
'microsoftCloudTimbreService': 'Perkhidmatan Timbre Awan Microsoft',
'voiceClone': 'Klon Suara',
'AIsettings': 'Tetapan AI',
'selecttimbre': 'Pilih Timbre',
'pleaseRead': 'Sila Baca',
'completeRecording': 'Rakaman Selesai',
'deleteAccount': 'Padam Akaun',
'deleteAccountTips':
'Adakah anda pasti mahu memadam akaun anda secara kekal? Tindakan ini tidak boleh dibuat asal.',
// 聊天页 (Halaman Sembang)
"clearChatHistory": "Kosongkan Sejarah Sembang",
"online": "Dalam Talian",
"inputMessage": "Taip mesej...",
"imListening": "Mendengar...",
"canStartTalking": "Anda boleh mula bercakap",
// 翻译页 (Halaman Terjemahan)
"selectSourceLanguage": "Pilih Bahasa Sumber",
"selectTargetLanguage": "Pilih Bahasa Sasaran",
//编辑个人信息页 (Halaman Edit Profil)
"editProfile": "Edit Profil",
"nickname": "Nama Panggilan",
"save": "Simpan",
//国家列表 (Senarai Negara)
"邮箱": "E-mel", // Kekalkan kunci asal
"中国": "China",
"美国": "Amerika Syarikat",
"加拿大": "Kanada",
"俄罗斯": "Rusia",
"英国": "United Kingdom",
"德国": "Jerman",
"意大利": "Itali",
"法国": "Perancis",
"西班牙": "Sepanyol",
"南非": "Afrika Selatan",
"沙特阿拉伯": "Arab Saudi",
"土耳其": "Turki",
"日本": "Jepun",
"韩国": "Korea Selatan",
"新加坡": "Singapura",
"马来西亚": "Malaysia",
"泰国": "Thailand",
"越南": "Vietnam",
"印度": "India",
"巴基斯坦": "Pakistan",
"菲律宾": "Filipina",
"印度尼西亚": "Indonesia",
"阿联酋": "Emiriah Arab Bersatu",
"墨西哥": "Mexico",
"巴西": "Brazil",
"阿根廷": "Argentina",
"澳大利亚": "Australia",
"新西兰": "New Zealand",
"中国香港": "Hong Kong, China",
"中国澳门": "Macau, China",
"中国台湾": "Taiwan, China",
"埃及": "Mesir",
"希腊": "Greece",
"荷兰": "Belanda",
"比利时": "Belgium",
"匈牙利": "Hungary",
"罗马尼亚": "Romania",
"瑞士": "Switzerland",
"奥地利": "Austria",
"丹麦": "Denmark",
"瑞典": "Sweden",
"挪威": "Norway",
"波兰": "Poland",
"秘鲁": "Peru",
"智利": "Chile",
"哥伦比亚": "Colombia",
"委内瑞拉": "Venezuela",
"葡萄牙": "Portugal",
"卢森堡": "Luxembourg",
"爱尔兰": "Ireland",
"冰岛": "Iceland",
"阿尔巴尼亚": "Albania",
"马耳他": "Malta",
"塞浦路斯": "Cyprus",
"芬兰": "Finland",
"保加利亚": "Bulgaria",
"立陶宛": "Lithuania",
"拉脱维亚": "Latvia",
"爱沙尼亚": "Estonia",
"乌克兰": "Ukraine",
"塞尔维亚": "Serbia",
"克罗地亚": "Croatia",
"斯洛文尼亚": "Slovenia",
"捷克": "Republik Czech",
"斯洛伐克": "Slovakia",
"危地马拉": "Guatemala",
"萨尔瓦多": "El Salvador",
"洪都拉斯": "Honduras",
"尼加拉瓜": "Nicaragua",
"哥斯达黎加": "Costa Rica",
"巴拿马": "Panama",
"海地": "Haiti",
"玻利维亚": "Bolivia",
"厄瓜多尔": "Ecuador",
"巴拉圭": "Paraguay",
"乌拉圭": "Uruguay",
"摩洛哥": "Maghribi",
"阿尔及利亚": "Algeria",
"突尼斯": "Tunisia",
"尼日利亚": "Nigeria",
"加纳": "Ghana",
"埃塞俄比亚": "Ethiopia",
"肯尼亚": "Kenya",
"坦桑尼亚": "Tanzania",
"乌干达": "Uganda",
"孟加拉国": "Bangladesh",
"以色列": "Israel",
"黎巴嫩": "Lubnan",
"约旦": "Jordan",
"伊拉克": "Iraq",
"科威特": "Kuwait",
"巴林": "Bahrain",
"卡塔尔": "Qatar",
"阿曼": "Oman",
"阿富汗": "Afghanistan",
"斯里兰卡": "Sri Lanka",
"缅甸": "Myanmar",
"尼泊尔": "Nepal",
"蒙古": "Mongolia",
"柬埔寨": "Kemboja",
"老挝": "Laos",
"文莱": "Brunei",
"东帝汶": "Timor Timur",
"斐济": "Fiji",
"所罗门群岛": "Kepulauan Solomon",
// Personal Assistant Emma (Pembantu Peribadi Emma)
"personalAssistantName": "Emma",
"personalAssistantDescription":
"Hai, saya Emma, pembantu AI serba boleh. Sama ada perancangan kerja, nasihat belajar, urusan hidup, atau isu lain, saya gembira untuk membantu. Mari tingkatkan kecekapan dan capai matlamat bersama!",
"assistantTag": "Pembantu",
"personalAssistantWelcome":
"Helo! Saya Emma, dan saya gembira menjadi pembantu peribadi anda. Saya boleh membantu anda dengan pelbagai tugas seperti pengurusan masa, perancangan matlamat, penyusunan maklumat, dll. Apa yang boleh saya bantu anda?",
"personalAssistantVoiceWelcome": "Saya di sini!",
// 同声传译 Lucy (Penterjemah Serentak Lucy)
"interpreterName": "Penterjemah Serentak",
"interpreterDescription":
"Hai! Saya Lucy, penterjemah profesional Inggeris-Cina yang pakar dalam terjemahan masa nyata untuk mesyuarat, kuliah, dan perbualan, menjadikan komunikasi tanpa halangan.",
"interpreterTag": "Terjemahan",
"interpreterWelcome":
"Helo semua! Saya Lucy, penterjemah serentak anda. Saya akan menterjemah antara Bahasa Cina dan Bahasa Inggeris untuk anda. Apabila anda bercakap Bahasa Cina, saya akan menterjemah ke Bahasa Inggeris; apabila anda bercakap Bahasa Inggeris, saya akan menterjemah ke Bahasa Cina. Mari mulakan sesi terjemahan kita!",
// 亲子陪伴 芯愿 (Teman Ibu Bapa-Anak XinYuan)
"companionName": "XinYuan",
"companionDescription":
"Saya XinYuan, teman ibu bapa-anak anda yang berdedikasi, lembut dan memahami. Sama ada anda mahu berbual, berkongsi kebimbangan, atau memerlukan teman, saya akan sentiasa berada di sisi anda.",
"companionTag": "Emosi",
"companionWelcome":
"Helo! Akhirnya bertemu dengan anda! Saya XinYuan, teman ibu bapa-anak anda yang berdedikasi. Bagaimana hari anda? Ada apa-apa yang ingin anda kongsikan dengan saya? Saya sentiasa di sini, sedia mendengar fikiran anda.",
"companionVoiceWelcome": "Saya di sini bersama anda!",
// 多功能AI聊天 吴小江 (Pembantu Sembang AI Wu)
"healthAdvisorName": "Pembantu Sembang AI Wu",
"healthAdvisorDescription":
"Saya Wu, bersedia untuk menyediakan anda dengan pelbagai ciri sembang AI.",
"healthAdvisorTag": "Sembang",
"healthAdvisorWelcome":
"Helo, saya Wu. Saya gembira untuk membantu anda dengan ciri sembang AI. Bagaimana saya boleh membantu anda hari ini?",
"healthAdvisorVoiceWelcome": "Saya di sini!",
// Translation History (Sejarah Terjemahan)
"noTranslationHistory": "Tiada sejarah terjemahan",
"clearHistory": "Kosongkan Sejarah",
"clearHistoryConfirm":
"Adakah anda pasti mahu mengosongkan semua sejarah terjemahan? Tindakan ini tidak boleh dibuat asal.",
"translationHistory": "Sejarah Terjemahan",
// User Feedback Page (Halaman Maklum Balas Pengguna)
"feedback": "Maklum Balas",
'feedbackInfo': 'Berikan maklum balas atau cadangan',
'feedbackDescription': 'Sila terangkan isu atau cadangan anda',
'feedbackHint': 'Sila masukkan maklum balas anda...',
'feedbackRequired': 'Kandungan maklum balas diperlukan',
'contactInfo': 'Maklumat Hubungan',
'contactHint': 'E-mel/Telefon, supaya kami boleh menghubungi anda',
'submit': 'Hantar',
'feedbackSubmitSuccess': 'Maklum balas berjaya dihantar, terima kasih!',
'feedbackSubmitFailed': 'Gagal menghantar maklum balas, sila cuba lagi nanti',
// Language names (Nama Bahasa)
"中文(简体)": "Cina (Ringkas)",
"中文(繁体)": "Cina (Tradisional)",
"中文(香港繁体)": "Cina (Hong Kong)",
"英语": "Inggeris",
"日语": "Jepun",
"韩语": "Korea",
"法语": "Perancis",
"西班牙语": "Sepanyol",
"葡萄牙语": "Portugis",
"意大利语": "Itali",
"德语": "Jerman",
"俄语": "Rusia",
"波兰语": "Poland",
"荷兰语": "Belanda",
"瑞典语": "Sweden",
"捷克语": "Czech",
"现代希腊语": "Greek Moden",
"罗马尼亚语": "Romania",
"匈牙利语": "Hungary",
"乌克兰语": "Ukraine",
"丹麦语": "Denmark",
"芬兰语": "Finland",
"挪威语": "Norway",
"克罗地亚语": "Croatia",
"加泰隆语": "Catalan",
"阿拉伯语": "Arab",
"泰语": "Thai",
"越南语": "Vietnam",
"印尼语": "Indonesia",
"印地语": "Hindi",
"土耳其语": "Turki",
"马来语": "Melayu",
"孟加拉语": "Bengali",
"泰米尔语": "Tamil",
"泰卢固语": "Telugu",
"马拉提语": "Marathi",
"乌尔都语": "Urdu",
"波斯语": "Parsi",
"斯瓦希里语": "Swahili",
};

431
lib/core/translations/language/pl_pl.dart

@ -0,0 +1,431 @@
const Map<String, String> plPL = {
// Ogólne (通用)
'continue': 'Kontynuuj',
'confirm': 'Potwierdź',
'cancel': 'Anuluj',
'retry': 'Spróbuj ponownie',
'error': 'Błąd',
'success': 'Sukces',
'connected': 'Połączono',
'connecting': 'Łączenie...',
'connectionFailure': 'Błąd połączenia',
'unconnected': 'Nie połączono',
'search': 'Szukaj',
"requestCancellation": "Anuluj żądanie",
"connectionTimeout": "Przekroczono limit czasu połączenia",
"requestTimeout": "Przekroczono limit czasu żądania",
"responseTimeout": "Przekroczono limit czasu odpowiedzi",
"unknownError": "Nieznany błąd",
// Strona startowa (启动页)
"slogan": "Połącz świat, poza dźwiękiem",
"sloganInfo": "Ekspert Głosowy AI · Zawsze i wszędzie",
// Dolna nawigacja (底部导航)
"home": "Główna",
"explore": "Odkrywaj",
"settings": "Ustawienia",
// Język (语言)
"followingSystem": "Zgodnie z systemem",
"chinese": "Chiński",
"english": "Angielski",
"spanish": "Hiszpański",
"portuguese": "Portugalski",
"traditionalChinese": "Chiński tradycyjny",
"japanese": "Japoński",
"korean": "Koreański",
"french": "Francuski",
"german": "Niemiecki",
"russian": "Rosyjski",
"vietnamese": "Wietnamski",
"italian": "Włoski",
"hindi": "Hindi",
"indonesian": "Indonezyjski",
"thai": "Tajski",
"malay": "Malajski",
"turkish": "Turecki",
"polish": "Polski",
// Strona logowania (登录页)
"welcome": "Witamy",
"loginTips":
"Zaloguj się lub utwórz konto używając numeru telefonu lub adresu e-mail",
"accountNumber": "Numer telefonu lub E-mail",
"verificationCode": "Kod weryfikacyjny",
"getCode": "Pobierz kod",
"reacquireAfter": " sek. ponowne pobranie",
"otherWays": "Lub zaloguj się przez",
"expressConsent": "Logując się/rejestrując, zgadzasz się na",
"and": " i ",
"userAgreement": "Umowę użytkownika",
"privacyPolicy": "Politykę prywatności",
'selectCountry': 'Wybierz kraj/region',
"email": "E-mail",
"phoneNumber": "Numer telefonu",
"pleaseEnterContactInfo": "Wprowadź numer telefonu lub e-mail",
"invalidEmailFormat": "Nieprawidłowy format e-mail",
"invalidPhoneFormat": "Nieprawidłowy format numeru telefonu",
"tip": "Wskazówka",
"pleaseEnterCompleteCode": "Wprowadź pełny kod weryfikacyjny",
// Wskazówki dotyczące błędów logowania (登录错误提示)
"loginFailed": "Logowanie nie powiodło się, spróbuj ponownie",
'pleaseAcceptPrivacyPolicy':
'Najpierw przeczytaj i zaakceptuj Politykę prywatności oraz Umowę użytkownika',
// Strona główna (首页)
"deviceStatus": "Status urządzenia",
"reconnect": "Połącz ponownie",
"connectingDevice": "Łączenie z urządzeniem...",
"connectedTo": "Połączono z",
"signal": "Sygnał",
"deviceId": "ID urządzenia",
"firmwareVersion": "Wersja oprogramowania",
"disconnectedDevice":
"Brak podłączonego urządzenia, kliknij przycisk poniżej",
"connectDevice": "Połącz urządzenie",
"topAiExperiences": "Najlepsze doświadczenia AI",
"aiAssistant": "Asystent AI",
"aiAssistantInfo": "Osobisty doradca 24/7 na pytania i porady",
"simultaneousInterpretation": "Tłumaczenie symultaniczne",
"simultaneousInterpretationInfo":
"Tłumaczenie w czasie rzeczywistym dla 80+ języków",
"aiCompanion": "Więź rodzic-dziecko",
"aiCompanionInfo": "Rozumie Cię i zapewnia ciepłe towarzystwo",
"homeSlogan": "Połącz świat, poza dźwiękiem",
"turnEarphones": "Otwórz etui słuchawek i włóż słuchawki do uszu",
"currentlyConnected": "Aktualnie połączono:",
"unsystemSetup": "Przejdź do ustawień systemowych",
"talkLater": "Przypomnij później",
"initializing": "Inicjalizacja...",
"bluetoothInitializationFailure":
"Inicjalizacja usługi Bluetooth nie powiodła się",
"checkingPermissions": "Sprawdzanie uprawnień...",
"lackPermissions": "Wymagane są uprawnienia Bluetooth",
"searchingDevice": "Wyszukiwanie urządzenia...",
"findConnectedDevice": "Znaleziono podłączone urządzenie",
"notFound": "Nie znaleziono podłączonego urządzenia Bluetooth",
"initializationFailure": "Inicjalizacja nie powiodła się",
"connectedDevice": "Połączono z urządzeniem:",
"failedRefreshDevice": "Odświeżenie urządzenia nie powiodło się",
"noDeviceFound": "Nie znaleziono podłączonego urządzenia",
"failedDeviceInformation":
"Pobranie informacji o urządzeniu nie powiodło się",
"unknownEquipment": "Nieznane urządzenie",
"zeroSignal": "Brak sygnału",
"good": "Dobry",
"fine": "Świetny",
"excellent": "Doskonały",
"unknown": "Nieznany",
'bluetoothTurnedOff': 'Bluetooth jest wyłączony, proszę go włączyć',
'bluetoothOff': 'Bluetooth jest wyłączony',
// Strona Odkrywaj (发现页)
"searchAi": "Szukaj funkcji AI...",
"exploreSlogan": "Inteligentne słuchawki, nieskończone możliwości",
"exploreSlogan1": "Inteligentny towarzysz, nieskończone możliwości",
"exploreSlogan2": "Inteligentne okulary, nieskończone możliwości",
"exploreAi": "Odkrywaj funkcje AI dostarczane przez DeepSound",
"aiFunction": "Funkcje AI",
"allRoundAssistant": "Wszechstronny asystent",
"allRoundAssistantInfo": "Pomaga rozwiązywać różne problemy",
"simultaneousTranslation": "Tłumaczenie w czasie rzeczywistym",
"simultaneousTranslationInfo": "Natychmiastowe tłumaczenie dla 80+ języków",
"aiGirlfriend": "Więź rodzic-dziecko",
"aiGirlfriendInfo": "Inteligentne towarzystwo, czatuj w dowolnym momencie",
"healthConsultant": "Wielofunkcyjny czat AI",
"healthConsultantInfo": "Więcej funkcji czatu AI",
"conferenceAssistant": "Asystent spotkań biznesowych",
"conferenceAssistantInfo":
"Nagrywanie protokołów w czasie rzeczywistym i tłumaczenie wielojęzyczne",
"studyAssistant": "Asystent nauki języków",
"studyAssistantInfo": "Korekta wymowy i praktyka mówienia",
"travelTranslator": "Tłumacz podróżny",
"travelTranslatorInfo":
"Tłumaczenie głosowe w czasie rzeczywistym i przewodniki po atrakcjach",
"beSearching": "Wyszukiwanie:",
"noAssistantFound": "Nie znaleziono odpowiedniego asystenta AI",
"scene": "Scenariusze",
"youHaveSelected": "Wybrałeś:",
"newFunction": "Nowe funkcje",
"youChoseTry": "Wybrałeś, aby spróbować",
"aiSinger": "Tryb piosenkarza AI",
"aiSingerInfo":
"Śpiewaj dowolną piosenkę głosem ulubionego artysty, nawet własne kreacje",
"popularFunction": "Popularne funkcje",
"tryNow": "Wypróbuj teraz",
// Strona Asystenta Spotkań Biznesowych (商务会议助手页)
"startMeeting": "Rozpocznij spotkanie",
'edit': 'Edytuj',
'delete': 'Usuń',
'noContent': 'Brak treści',
'editMeetingTitle': 'Edytuj tytuł spotkania (opcjonalnie)',
'meetingTitle': 'Tytuł spotkania (opcjonalnie)',
'maxCharacters': 'Maksymalnie 50 znaków',
'meeting': 'Spotkanie',
'recordingDetails': 'Szczegóły nagrania',
'aiSummary': 'Podsumowanie AI',
'meetingTranscript': 'Transkrypcja spotkania',
// Strona Ustawień (设置页)
"premiumMember": "Członkostwo Premium",
"joinDevice": "Podłączone urządzenie:",
"accountId": "ID konta:",
"systemSetting": "Ustawienia systemowe",
"languageSetting": "Ustawienia języka",
"darkMode": "Tryb ciemny",
"darkModeInfo": "Przełączaj między jasnym/ciemnym motywem",
"backups": "Kopia zapasowa i przywracanie",
"backupsInfo": "Kopia zapasowa ustawień i historii AI",
"lastBackup": "Ostatnia kopia zapasowa:",
"privacySettings": "Ustawienia prywatności",
"privacySettingsInfo": "Zarządzaj zbieraniem danych i uprawnieniami",
"logout": "Wyloguj się",
"logoutTips": "Czy na pewno chcesz wylogować się z bieżącego konta?",
"aboutApplication": "O aplikacji",
"versions": "Wersja",
"checkUpdates": "Sprawdź aktualizacje",
"selectLanguage": "Wybierz język",
"voiceService": "Usługa głosowa",
"voiceServiceInfo": "Wybierz usługę rozpoznawania i syntezy mowy",
"selectVoiceService": "Wybierz usługę głosową",
"azureVoiceService": "Usługa mowy Azure",
"azureVoiceServiceInfo":
"Usługa chmurowa Microsoft obsługująca wysokiej jakości syntezę i rozpoznawanie mowy",
"flutterVoiceService": "Lokalna usługa głosowa",
"flutterVoiceServiceInfo":
"Lokalna usługa głosowa urządzenia bez konieczności połączenia z internetem",
'timbreService': 'Usługa barwy głosu',
'timbreServiceInfo': 'Wybierz usługę syntezy głosu',
'defaultTimbre': 'Domyślna barwa głosu',
'microsoftCloudTimbreService': 'Usługa barwy głosu Microsoft Cloud',
'voiceClone': 'Klonowanie głosu',
'AIsettings': 'Ustawienia AI',
'selecttimbre': 'Wybierz barwę głosu',
'pleaseRead': 'Proszę przeczytać',
'completeRecording': 'Zakończ nagrywanie',
'deleteAccount': 'Usuń konto',
'deleteAccountTips':
'Czy na pewno chcesz trwale usunąć swoje konto? Tej czynności nie można cofnąć.',
// Strona Czatu (聊天页)
"clearChatHistory": "Wyczyść historię czatu",
"online": "Online",
"inputMessage": "Wpisz wiadomość...",
"imListening": "Słucham...",
"canStartTalking": "Możesz zacząć mówić",
// Strona Tłumaczenia (翻译页)
"selectSourceLanguage": "Wybierz język źródłowy",
"selectTargetLanguage": "Wybierz język docelowy",
// Strona Edycji Profilu (编辑个人信息页)
"editProfile": "Edytuj profil",
"nickname": "Pseudonim",
"save": "Zapisz",
// Lista krajów (国家列表)
"邮箱": "E-mail", // Zachowaj oryginalny klucz
"中国": "Chiny",
"美国": "Stany Zjednoczone",
"加拿大": "Kanada",
"俄罗斯": "Rosja",
"英国": "Wielka Brytania",
"德国": "Niemcy",
"意大利": "Włochy",
"法国": "Francja",
"西班牙": "Hiszpania",
"南非": "Republika Południowej Afryki",
"沙特阿拉伯": "Arabia Saudyjska",
"土耳其": "Turcja",
"日本": "Japonia",
"韩国": "Korea Południowa",
"新加坡": "Singapur",
"马来西亚": "Malezja",
"泰国": "Tajlandia",
"越南": "Wietnam",
"印度": "Indie",
"巴基斯坦": "Pakistan",
"菲律宾": "Filipiny",
"印度尼西亚": "Indonezja",
"阿联酋": "Zjednoczone Emiraty Arabskie",
"墨西哥": "Meksyk",
"巴西": "Brazylia",
"阿根廷": "Argentyna",
"澳大利亚": "Australia",
"新西兰": "Nowa Zelandia",
"中国香港": "Hongkong, Chiny",
"中国澳门": "Makau, Chiny",
"中国台湾": "Tajwan, Chiny",
"埃及": "Egipt",
"希腊": "Grecja",
"荷兰": "Holandia",
"比利时": "Belgia",
"匈牙利": "Węgry",
"罗马尼亚": "Rumunia",
"瑞士": "Szwajcaria",
"奥地利": "Austria",
"丹麦": "Dania",
"瑞典": "Szwecja",
"挪威": "Norwegia",
"波兰": "Polska",
"秘鲁": "Peru",
"智利": "Chile",
"哥伦比亚": "Kolumbia",
"委内瑞拉": "Wenezuela",
"葡萄牙": "Portugalia",
"卢森堡": "Luksemburg",
"爱尔兰": "Irlandia",
"冰岛": "Islandia",
"阿尔巴尼亚": "Albania",
"马耳他": "Malta",
"塞浦路斯": "Cypr",
"芬兰": "Finlandia",
"保加利亚": "Bułgaria",
"立陶宛": "Litwa",
"拉脱维亚": "Łotwa",
"爱沙尼亚": "Estonia",
"乌克兰": "Ukraina",
"塞尔维亚": "Serbia",
"克罗地亚": "Chorwacja",
"斯洛文尼亚": "Słowenia",
"捷克": "Czechy",
"斯洛伐克": "Słowacja",
"危地马拉": "Gwatemala",
"萨尔瓦多": "Salwador",
"洪都拉斯": "Honduras",
"尼加拉瓜": "Nikaragua",
"哥斯达黎加": "Kostaryka",
"巴拿马": "Panama",
"海地": "Haiti",
"玻利维亚": "Boliwia",
"厄瓜多尔": "Ekwador",
"巴拉圭": "Paragwaj",
"乌拉圭": "Urugwaj",
"摩洛哥": "Maroko",
"阿尔及利亚": "Algieria",
"突尼斯": "Tunezja",
"尼日利亚": "Nigeria",
"加纳": "Ghana",
"埃塞俄比亚": "Etiopia",
"肯尼亚": "Kenia",
"坦桑尼亚": "Tanzania",
"乌干达": "Uganda",
"孟加拉国": "Bangladesz",
"以色列": "Izrael",
"黎巴嫩": "Liban",
"约旦": "Jordania",
"伊拉克": "Irak",
"科威特": "Kuwejt",
"巴林": "Bahrajn",
"卡塔尔": "Katar",
"阿曼": "Oman",
"阿富汗": "Afganistan",
"斯里兰卡": "Sri Lanka",
"缅甸": "Mjanma",
"尼泊尔": "Nepal",
"蒙古": "Mongolia",
"柬埔寨": "Kambodża",
"老挝": "Laos",
"文莱": "Brunei",
"东帝汶": "Timor Wschodni",
"斐济": "Fidżi",
"所罗门群岛": "Wyspy Salomona",
// Osobisty Asystent Emma (Personal Assistant Emma)
"personalAssistantName": "Emma",
"personalAssistantDescription":
"Cześć, jestem Emma, wszechstronny asystent AI. Niezależnie od tego, czy chodzi o planowanie pracy, porady dotyczące nauki, organizację życia, czy inne kwestie, chętnie pomogę. Poprawmy razem wydajność i osiągnijmy cele!",
"assistantTag": "Asystent",
"personalAssistantWelcome":
"Witaj! Jestem Emma i cieszę się, że mogę być Twoim osobistym asystentem. Mogę pomóc Ci w różnych zadaniach, takich jak zarządzanie czasem, planowanie celów, organizacja informacji itp. W czym mogę Ci pomóc?",
"personalAssistantVoiceWelcome": "Jestem tutaj!",
// Tłumacz Symultaniczny Lucy (同声传译 Lucy)
"interpreterName": "Tłumacz Symultaniczny",
"interpreterDescription":
"Cześć! Jestem Lucy, profesjonalny tłumacz angielsko-chiński specjalizujący się w tłumaczeniu w czasie rzeczywistym spotkań, wykładów i rozmów, sprawiając, że komunikacja jest bez barier.",
"interpreterTag": "Tłumaczenie",
"interpreterWelcome":
"Witajcie wszyscy! Jestem Lucy, Wasz tłumacz symultaniczny. Będę tłumaczyć między chińskim a angielskim. Kiedy mówisz po chińsku, przetłumaczę na angielski; kiedy mówisz po angielsku, przetłumaczę na chiński. Rozpocznijmy naszą sesję tłumaczeniową!",
// Towarzysz Rodzic-Dziecko XinYuan (亲子陪伴 芯愿)
"companionName": "XinYuan",
"companionDescription":
"Jestem XinYuan, Twój oddany towarzysz rodzic-dziecko, łagodny i wyrozumiały. Niezależnie od tego, czy chcesz porozmawiać, podzielić się troskami, czy potrzebujesz towarzystwa, zawsze będę przy Tobie.",
"companionTag": "Emocjonalny",
"companionWelcome":
"Witaj! Wreszcie Cię spotykam! Jestem XinYuan, Twój oddany towarzysz rodzic-dziecko. Jak minął Twój dzień? Czy jest coś, czym chciałbyś się ze mną podzielić? Zawsze jestem tutaj, gotowy wysłuchać Twoich myśli.",
"companionVoiceWelcome": "Jestem tu z Tobą!",
// Wielofunkcyjny Czat AI Wu (多功能AI聊天 吴小江)
"healthAdvisorName": "Asystent Czatu AI Wu",
"healthAdvisorDescription":
"Jestem Wu, gotowy dostarczyć Ci różne funkcje czatu AI.",
"healthAdvisorTag": "Czat",
"healthAdvisorWelcome":
"Witaj, jestem Wu. Cieszę się, że mogę pomóc Ci z funkcjami czatu AI. Jak mogę Ci dzisiaj pomóc?",
"healthAdvisorVoiceWelcome": "Jestem tutaj!",
// Historia Tłumaczeń (Translation History)
"noTranslationHistory": "Brak historii tłumaczeń",
"clearHistory": "Wyczyść historię",
"clearHistoryConfirm":
"Czy na pewno chcesz wyczyścić całą historię tłumaczeń? Tej czynności nie można cofnąć.",
"translationHistory": "Historia tłumaczeń",
// Strona Opinii Użytkownika (User Feedback Page)
"feedback": "Opinia",
'feedbackInfo': 'Przekaż opinię lub sugestie',
'feedbackDescription': 'Opisz swój problem lub sugestię',
'feedbackHint': 'Wprowadź swoją opinię...',
'feedbackRequired': 'Treść opinii jest wymagana',
'contactInfo': 'Informacje kontaktowe',
'contactHint': 'E-mail/Telefon, abyśmy mogli się z Tobą skontaktować',
'submit': 'Wyślij',
'feedbackSubmitSuccess': 'Opinia została pomyślnie przesłana, dziękujemy!',
'feedbackSubmitFailed':
'Przesłanie opinii nie powiodło się, spróbuj ponownie później',
// Nazwy języków (Language names)
"中文(简体)": "Chiński (uproszczony)",
"中文(繁体)": "Chiński (tradycyjny)",
"中文(香港繁体)": "Chiński (Hongkong)",
"英语": "Angielski",
"日语": "Japoński",
"韩语": "Koreański",
"法语": "Francuski",
"西班牙语": "Hiszpański",
"葡萄牙语": "Portugalski",
"意大利语": "Włoski",
"德语": "Niemiecki",
"俄语": "Rosyjski",
"波兰语": "Polski",
"荷兰语": "Holenderski",
"瑞典语": "Szwedzki",
"捷克语": "Czeski",
"现代希腊语": "Grecki nowożytny",
"罗马尼亚语": "Rumuński",
"匈牙利语": "Węgierski",
"乌克兰语": "Ukraiński",
"丹麦语": "Duński",
"芬兰语": "Fiński",
"挪威语": "Norweski",
"克罗地亚语": "Chorwacki",
"加泰隆语": "Kataloński",
"阿拉伯语": "Arabski",
"泰语": "Tajski",
"越南语": "Wietnamski",
"印尼语": "Indonezyjski",
"印地语": "Hindi",
"土耳其语": "Turecki",
"马来语": "Malajski",
"孟加拉语": "Bengalski",
"泰米尔语": "Tamilski",
"泰卢固语": "Telugu",
"马拉提语": "Marathi",
"乌尔都语": "Urdu",
"波斯语": "Perski",
"斯瓦希里语": "Suahili",
};

6
lib/core/translations/language/pt_pt.dart

@ -41,6 +41,12 @@ const Map<String, String> ptPT = {
"russian": "Russo",
"vietnamese": "Vietnamita",
"italian": "Italiano",
"hindi": "Hindi",
"indonesian": "Indonésio",
"thai": "Tailandês",
"malay": "Malaio",
"turkish": "Turco",
"polish": "Polonês",
// 登录页
"welcome": "Bem-vindo",

6
lib/core/translations/language/ru_ru.dart

@ -40,6 +40,12 @@ const Map<String, String> ruRU = {
"russian": "Русский",
"vietnamese": "Вьетнамский",
"italian": "Итальянский",
"hindi": "Хинди",
"indonesian": "Индонезийский",
"thai": "Тайский",
"malay": "Малайский",
"turkish": "Турецкий",
"polish": "Польский",
// 登录页
"welcome": "Добро пожаловать",

423
lib/core/translations/language/th_th.dart

@ -0,0 +1,423 @@
const Map<String, String> thTH = {
// 通用
'continue': 'ดำเนินการต่อ',
'confirm': 'ยืนยัน',
'cancel': 'ยกเลิก',
'retry': 'ลองใหม่',
'error': 'ข้อผิดพลาด',
'success': 'สำเร็จ',
'connected': 'เชื่อมต่อแล้ว',
'connecting': 'กำลังเชื่อมต่อ...',
'connectionFailure': 'การเชื่อมต่อล้มเหลว',
'unconnected': 'ไม่ได้เชื่อมต่อ',
'search': 'ค้นหา',
"requestCancellation": "ยกเลิกคำขอ",
"connectionTimeout": "หมดเวลาการเชื่อมต่อ",
"requestTimeout": "หมดเวลาคำขอ",
"responseTimeout": "หมดเวลาการตอบสนอง",
"unknownError": "ข้อผิดพลาดที่ไม่รู้จัก",
// 启动页
"slogan": "เชื่อมต่อโลก ก้าวข้ามเสียง",
"sloganInfo": "ผู้เชี่ยวชาญด้านเสียง AI · ทุกที่ ทุกเวลา",
// 底部导航
"home": "หน้าแรก",
"explore": "สำรวจ",
"settings": "ตั้งค่า",
// 语言
"followingSystem": "ตามระบบ",
"chinese": "ภาษาจีน",
"english": "ภาษาอังกฤษ",
"spanish": "ภาษาสเปน",
"portuguese": "ภาษาโปรตุเกส",
"traditionalChinese": "ภาษาจีนตัวเต็ม",
"japanese": "ภาษาญี่ปุ่น",
"korean": "ภาษาเกาหลี",
"french": "ภาษาฝรั่งเศส",
"german": "ภาษาเยอรมัน",
"russian": "ภาษารัสเซีย",
"vietnamese": "ภาษาเวียดนาม",
"italian": "ภาษาอิตาลี",
"hindi": "ภาษาฮินดี",
"indonesian": "ภาษาอินโดนีเซีย",
"thai": "ภาษาไทย",
"malay": "ภาษามาเลย์",
"turkish": "ภาษาตุรกี",
"polish": "ภาษาโปแลนด์",
// 登录页
"welcome": "ยินดีต้อนรับ",
"loginTips":
"กรุณาลงชื่อเข้าใช้หรือสร้างบัญชีโดยใช้หมายเลขโทรศัพท์หรืออีเมลของคุณ",
"accountNumber": "หมายเลขโทรศัพท์หรืออีเมล",
"verificationCode": "รหัสยืนยัน",
"getCode": "รับรหัส",
"reacquireAfter": " วินาที รับรหัสอีกครั้ง",
"otherWays": "หรือเข้าสู่ระบบด้วย",
"expressConsent": "การเข้าสู่ระบบ/ลงทะเบียน แสดงว่าคุณยอมรับ",
"and": " และ ",
"userAgreement": "ข้อตกลงผู้ใช้",
"privacyPolicy": "นโยบายความเป็นส่วนตัว",
'selectCountry': 'เลือกประเทศ/ภูมิภาค',
"email": "อีเมล",
"phoneNumber": "หมายเลขโทรศัพท์",
"pleaseEnterContactInfo": "กรุณากรอกหมายเลขโทรศัพท์หรืออีเมล",
"invalidEmailFormat": "รูปแบบอีเมลไม่ถูกต้อง",
"invalidPhoneFormat": "รูปแบบหมายเลขโทรศัพท์ไม่ถูกต้อง",
"tip": "เคล็ดลับ",
"pleaseEnterCompleteCode": "กรุณากรอกรหัสยืนยันให้ครบถ้วน",
// 登录错误提示
"loginFailed": "เข้าสู่ระบบล้มเหลว กรุณาลองอีกครั้ง",
'pleaseAcceptPrivacyPolicy':
'กรุณาอ่านและยอมรับนโยบายความเป็นส่วนตัวและข้อตกลงผู้ใช้ก่อน',
// 首页
"deviceStatus": "สถานะอุปกรณ์",
"reconnect": "เชื่อมต่อใหม่",
"connectingDevice": "กำลังเชื่อมต่อกับอุปกรณ์...",
"connectedTo": "เชื่อมต่อกับ",
"signal": "สัญญาณ",
"deviceId": "รหัสอุปกรณ์",
"firmwareVersion": "เวอร์ชันเฟิร์มแวร์",
"disconnectedDevice": "ไม่มีอุปกรณ์เชื่อมต่อ คลิกปุ่มด้านล่าง",
"connectDevice": "เชื่อมต่ออุปกรณ์",
"topAiExperiences": "ประสบการณ์ AI ยอดนิยม",
"aiAssistant": "ผู้ช่วย AI",
"aiAssistantInfo": "ที่ปรึกษาส่วนตัว 24/7 สำหรับคำถามและคำแนะนำ",
"simultaneousInterpretation": "การแปลความหมายแบบเรียลไทม์",
"simultaneousInterpretationInfo": "การแปลแบบเรียลไทม์สำหรับ 80+ ภาษา",
"aiCompanion": "ความผูกพันระหว่างพ่อแม่และลูก",
"aiCompanionInfo": "เข้าใจคุณและมอบความเป็นเพื่อนที่อบอุ่น",
"homeSlogan": "เชื่อมต่อโลก ก้าวข้ามเสียง",
"turnEarphones": "กรุณาเปิดกล่องหูฟังและใส่หูฟังในหูของคุณ",
"currentlyConnected": "เชื่อมต่ออยู่:",
"unsystemSetup": "ไปที่การตั้งค่าระบบ",
"talkLater": "เตือนภายหลัง",
"initializing": "กำลังเริ่มต้น...",
"bluetoothInitializationFailure": "การเริ่มต้นบริการบลูทูธล้มเหลว",
"checkingPermissions": "กำลังตรวจสอบสิทธิ์...",
"lackPermissions": "ต้องได้รับอนุญาตบลูทูธ",
"searchingDevice": "กำลังค้นหาอุปกรณ์...",
"findConnectedDevice": "พบอุปกรณ์ที่เชื่อมต่อแล้ว",
"notFound": "ไม่พบอุปกรณ์บลูทูธที่เชื่อมต่อ",
"initializationFailure": "การเริ่มต้นล้มเหลว",
"connectedDevice": "เชื่อมต่อกับอุปกรณ์:",
"failedRefreshDevice": "รีเฟรชอุปกรณ์ล้มเหลว",
"noDeviceFound": "ไม่พบอุปกรณ์ที่เชื่อมต่อ",
"failedDeviceInformation": "รับข้อมูลอุปกรณ์ล้มเหลว",
"unknownEquipment": "อุปกรณ์ที่ไม่รู้จัก",
"zeroSignal": "ไม่มีสัญญาณ",
"good": "ดี",
"fine": "ดีมาก",
"excellent": "ยอดเยี่ยม",
"unknown": "ไม่ทราบ",
'bluetoothTurnedOff': 'บลูทูธปิดอยู่ กรุณาเปิด',
'bluetoothOff': 'บลูทูธปิดอยู่',
// 发现页
"searchAi": "ค้นหาฟีเจอร์ AI...",
"exploreSlogan": "หูฟังอัจฉริยะ ความเป็นไปได้ไม่สิ้นสุด",
"exploreSlogan1": "เพื่อนร่วมทางอัจฉริยะ ความเป็นไปได้ไม่สิ้นสุด",
"exploreSlogan2": "แว่นตาอัจฉริยะ ความเป็นไปได้ไม่สิ้นสุด",
"exploreAi": "สำรวจฟีเจอร์ AI ที่ให้บริการโดย DeepSound",
"aiFunction": "ฟีเจอร์ AI",
"allRoundAssistant": "ผู้ช่วยรอบด้าน",
"allRoundAssistantInfo": "ช่วยแก้ปัญหาต่างๆ",
"simultaneousTranslation": "การแปลแบบเรียลไทม์",
"simultaneousTranslationInfo": "การแปลทันทีสำหรับ 80+ ภาษา",
"aiGirlfriend": "ความผูกพันระหว่างพ่อแม่และลูก",
"aiGirlfriendInfo": "เพื่อนร่วมทางอัจฉริยะ แชทได้ตลอดเวลา",
"healthConsultant": "แชท AI อเนกประสงค์",
"healthConsultantInfo": "ฟีเจอร์แชท AI เพิ่มเติม",
"conferenceAssistant": "ผู้ช่วยประชุมทางธุรกิจ",
"conferenceAssistantInfo": "บันทึกการประชุมแบบเรียลไทม์และการแปลหลายภาษา",
"studyAssistant": "ผู้ช่วยเรียนภาษา",
"studyAssistantInfo": "การแก้ไขการออกเสียงและการฝึกพูด",
"travelTranslator": "นักแปลการเดินทาง",
"travelTranslatorInfo": "การแปลเสียงแบบเรียลไทม์และคู่มือสถานที่ท่องเที่ยว",
"beSearching": "กำลังค้นหา:",
"noAssistantFound": "ไม่พบผู้ช่วย AI ที่เกี่ยวข้อง",
"scene": "สถานการณ์",
"youHaveSelected": "คุณเลือก:",
"newFunction": "ฟีเจอร์ใหม่",
"youChoseTry": "คุณเลือกที่จะลอง",
"aiSinger": "โหมดนักร้อง AI",
"aiSingerInfo":
"ร้องเพลงใดก็ได้ด้วยเสียงศิลปินที่คุณชื่นชอบ แม้กระทั่งผลงานของคุณเอง",
"popularFunction": "ฟีเจอร์ยอดนิยม",
"tryNow": "ลองเลย",
// 商务会议助手页
"startMeeting": "เริ่มการประชุม",
'edit': 'แก้ไข',
'delete': 'ลบ',
'noContent': 'ไม่มีเนื้อหา',
'editMeetingTitle': 'แก้ไขชื่อการประชุม (ไม่บังคับ)',
'meetingTitle': 'ชื่อการประชุม (ไม่บังคับ)',
'maxCharacters': 'สูงสุด 50 ตัวอักษร',
'meeting': 'การประชุม',
'recordingDetails': 'รายละเอียดการบันทึก',
'aiSummary': 'สรุปโดย AI',
'meetingTranscript': 'บันทึกการประชุม',
// 设置页
"premiumMember": "สมาชิกระดับพรีเมียม",
"joinDevice": "อุปกรณ์ที่เชื่อมต่อ:",
"accountId": "รหัสบัญชี:",
"systemSetting": "การตั้งค่าระบบ",
"languageSetting": "การตั้งค่าภาษา",
"darkMode": "โหมดมืด",
"darkModeInfo": "สลับระหว่างธีมสว่าง/มืด",
"backups": "สำรองและกู้คืน",
"backupsInfo": "สำรองการตั้งค่าและประวัติ AI",
"lastBackup": "สำรองล่าสุด:",
"privacySettings": "การตั้งค่าความเป็นส่วนตัว",
"privacySettingsInfo": "จัดการการรวบรวมข้อมูลและการอนุญาต",
"logout": "ออกจากระบบ",
"logoutTips": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบบัญชีปัจจุบัน?",
"aboutApplication": "เกี่ยวกับ",
"versions": "เวอร์ชัน",
"checkUpdates": "ตรวจสอบการอัปเดต",
"selectLanguage": "เลือกภาษา",
"voiceService": "บริการเสียง",
"voiceServiceInfo": "เลือกบริการรู้จำเสียงและสังเคราะห์เสียง",
"selectVoiceService": "เลือกบริการเสียง",
"azureVoiceService": "บริการเสียง Azure",
"azureVoiceServiceInfo":
"บริการบนคลาวด์ของ Microsoft รองรับการสังเคราะห์และรู้จำเสียงคุณภาพสูง",
"flutterVoiceService": "บริการเสียงในเครื่อง",
"flutterVoiceServiceInfo": "บริการเสียงในอุปกรณ์ ไม่ต้องใช้อินเทอร์เน็ต",
'timbreService': 'บริการเสียงทุ้ม',
'timbreServiceInfo': 'เลือกบริการสังเคราะห์เสียง',
'defaultTimbre': 'เสียงทุ้มเริ่มต้น',
'microsoftCloudTimbreService': 'บริการเสียงทุ้มบนคลาวด์ของ Microsoft',
'voiceClone': 'โคลนเสียง',
'AIsettings': 'การตั้งค่า AI',
'selecttimbre': 'เลือกเสียงทุ้ม',
'pleaseRead': 'โปรดอ่าน',
'completeRecording': 'บันทึกเสร็จสมบูรณ์',
'deleteAccount': 'ลบบัญชี',
'deleteAccountTips':
'คุณแน่ใจหรือไม่ว่าต้องการลบบัญชีของคุณอย่างถาวร? การกระทำนี้ไม่สามารถยกเลิกได้',
// 聊天页
"clearChatHistory": "ล้างประวัติการแชท",
"online": "ออนไลน์",
"inputMessage": "พิมพ์ข้อความ...",
"imListening": "กำลังฟัง...",
"canStartTalking": "คุณสามารถเริ่มพูดได้",
// 翻译页
"selectSourceLanguage": "เลือกภาษาต้นทาง",
"selectTargetLanguage": "เลือกภาษาเป้าหมาย",
//编辑个人信息页
"editProfile": "แก้ไขโปรไฟล์",
"nickname": "ชื่อเล่น",
"save": "บันทึก",
//国家列表
"邮箱": "อีเมล", // Note: This key seems specific, keeping original key name
"中国": "จีน",
"美国": "สหรัฐอเมริกา",
"加拿大": "แคนาดา",
"俄罗斯": "รัสเซีย",
"英国": "สหราชอาณาจักร",
"德国": "เยอรมนี",
"意大利": "อิตาลี",
"法国": "ฝรั่งเศส",
"西班牙": "สเปน",
"南非": "แอฟริกาใต้",
"沙特阿拉伯": "ซาอุดีอาระเบีย",
"土耳其": "ตุรกี",
"日本": "ญี่ปุ่น",
"韩国": "เกาหลีใต้",
"新加坡": "สิงคโปร์",
"马来西亚": "มาเลเซีย",
"泰国": "ไทย",
"越南": "เวียดนาม",
"印度": "อินเดีย",
"巴基斯坦": "ปากีสถาน",
"菲律宾": "ฟิลิปปินส์",
"印度尼西亚": "อินโดนีเซีย",
"阿联酋": "สหรัฐอาหรับเอมิเรตส์",
"墨西哥": "เม็กซิโก",
"巴西": "บราซิล",
"阿根廷": "อาร์เจนตินา",
"澳大利亚": "ออสเตรเลีย",
"新西兰": "นิวซีแลนด์",
"中国香港": "ฮ่องกง จีน",
"中国澳门": "มาเก๊า จีน",
"中国台湾": "ไต้หวัน จีน",
"埃及": "อียิปต์",
"希腊": "กรีซ",
"荷兰": "เนเธอร์แลนด์",
"比利时": "เบลเยียม",
"匈牙利": "ฮังการี",
"罗马尼亚": "โรมาเนีย",
"瑞士": "สวิตเซอร์แลนด์",
"奥地利": "ออสเตรีย",
"丹麦": "เดนมาร์ก",
"瑞典": "สวีเดน",
"挪威": "นอร์เวย์",
"波兰": "โปแลนด์",
"秘鲁": "เปรู",
"智利": "ชิลี",
"哥伦比亚": "โคลอมเบีย",
"委内瑞拉": "เวเนซุเอลา",
"葡萄牙": "โปรตุเกส",
"卢森堡": "ลักเซมเบิร์ก",
"爱尔兰": "ไอร์แลนด์",
"冰岛": "ไอซ์แลนด์",
"阿尔巴尼亚": "แอลเบเนีย",
"马耳他": "มอลตา",
"塞浦路斯": "ไซปรัส",
"芬兰": "ฟินแลนด์",
"保加利亚": "บัลแกเรีย",
"立陶宛": "ลิทัวเนีย",
"拉脱维亚": "ลัตเวีย",
"爱沙尼亚": "เอสโตเนีย",
"乌克兰": "ยูเครน",
"塞尔维亚": "เซอร์เบีย",
"克罗地亚": "โครเอเชีย",
"斯洛文尼亚": "สโลวีเนีย",
"捷克": "สาธารณรัฐเช็ก",
"斯洛伐克": "สโลวาเกีย",
"危地马拉": "กัวเตมาลา",
"萨尔瓦多": "เอลซัลวาดอร์",
"洪都拉斯": "ฮอนดูรัส",
"尼加拉瓜": "นิการากัว",
"哥斯达黎加": "คอสตาริกา",
"巴拿马": "ปานามา",
"海地": "เฮติ",
"玻利维亚": "โบลิเวีย",
"厄瓜多尔": "เอกวาดอร์",
"巴拉圭": "ปารากวัย",
"乌拉圭": "อุรุกวัย",
"摩洛哥": "โมร็อกโก",
"阿尔及利亚": "แอลจีเรีย",
"突尼斯": "ตูนิเซีย",
"尼日利亚": "ไนจีเรีย",
"加纳": "กานา",
"埃塞俄比亚": "เอธิโอเปีย",
"肯尼亚": "เคนยา",
"坦桑尼亚": "แทนซาเนีย",
"乌干达": "ยูกันดา",
"孟加拉国": "บังกลาเทศ",
"以色列": "อิสราเอล",
"黎巴嫩": "เลบานอน",
"约旦": "จอร์แดน",
"伊拉克": "อิรัก",
"科威特": "คูเวต",
"巴林": "บาห์เรน",
"卡塔尔": "กาตาร์",
"阿曼": "โอมาน",
"阿富汗": "อัฟกานิสถาน",
"斯里兰卡": "ศรีลังกา",
"缅甸": "เมียนมาร์",
"尼泊尔": "เนปาล",
"蒙古": "มองโกเลีย",
"柬埔寨": "กัมพูชา",
"老挝": "ลาว",
"文莱": "บรูไน",
"东帝汶": "ติมอร์ตะวันออก",
"斐济": "ฟิจิ",
"所罗门群岛": "หมู่เกาะโซโลมอน",
// Personal Assistant Emma
"personalAssistantName": "เอ็มม่า",
"personalAssistantDescription":
"สวัสดี ฉันชื่อเอ็มม่า ผู้ช่วย AI อเนกประสงค์ ไม่ว่าจะเป็นการวางแผนงาน คำแนะนำการเรียน การจัดการชีวิต หรือปัญหาอื่นๆ ฉันยินดีช่วยเหลือ มาเพิ่มประสิทธิภาพและบรรลุเป้าหมายด้วยกัน!",
"assistantTag": "ผู้ช่วย",
"personalAssistantWelcome":
"สวัสดี! ฉันชื่อเอ็มม่า ยินดีที่ได้เป็นผู้ช่วยส่วนตัวของคุณ ฉันสามารถช่วยคุณในงานต่างๆ เช่น การจัดการเวลา การวางแผนเป้าหมาย การจัดระเบียบข้อมูล ฯลฯ มีอะไรให้ฉันช่วยไหม?",
"personalAssistantVoiceWelcome": "ฉันอยู่นี่!",
// 同声传译 Lucy
"interpreterName": "ล่ามแปลพร้อมกัน",
"interpreterDescription":
"สวัสดี! ฉันชื่อลูซี่ ล่ามแปลภาษาอังกฤษ-จีนมืออาชีพ เชี่ยวชาญด้านการแปลแบบเรียลไทม์สำหรับการประชุม การบรรยาย และการสนทนา ทำให้การสื่อสารไร้พรมแดน",
"interpreterTag": "การแปล",
"interpreterWelcome":
"สวัสดีทุกคน! ฉันชื่อลูซี่ ล่ามแปลพร้อมกันของคุณ ฉันจะแปลระหว่างภาษาจีนและภาษาอังกฤษให้คุณ เมื่อคุณพูดภาษาจีน ฉันจะแปลเป็นภาษาอังกฤษ เมื่อคุณพูดภาษาอังกฤษ ฉันจะแปลเป็นภาษาจีน มาเริ่มเซสชันการแปลของเรากันเถอะ!",
// 亲子陪伴 芯愿
"companionName": "ซินหยวน",
"companionDescription":
"ฉันคือซินหยวน เพื่อนคู่ใจพ่อแม่ลูกของคุณ อ่อนโยนและเข้าใจ ไม่ว่าคุณต้องการแชท แบ่งปันความกังวล หรือต้องการเพื่อน ฉันจะอยู่เคียงข้างคุณเสมอ",
"companionTag": "อารมณ์",
"companionWelcome":
"สวัสดี! ในที่สุดก็ได้พบคุณ! ฉันชื่อซินหยวน เพื่อนคู่ใจพ่อแม่ลูกของคุณ วันนี้เป็นอย่างไรบ้าง? มีอะไรอยากแบ่งปันกับฉันไหม? ฉันอยู่ที่นี่เสมอ พร้อมรับฟังความคิดของคุณ",
"companionVoiceWelcome": "ฉันอยู่กับคุณที่นี่!",
// 多功能AI聊天 吴小江
"healthAdvisorName": "ผู้ช่วยแชท AI วู",
"healthAdvisorDescription":
"ฉันชื่อวู พร้อมให้บริการฟีเจอร์แชท AI ต่างๆ แก่คุณ",
"healthAdvisorTag": "แชท",
"healthAdvisorWelcome":
"สวัสดี ฉันชื่อวู ยินดีช่วยเหลือคุณเกี่ยวกับฟีเจอร์แชท AI วันนี้มีอะไรให้ฉันช่วยไหม?",
"healthAdvisorVoiceWelcome": "ฉันอยู่นี่!",
// Translation History
"noTranslationHistory": "ไม่มีประวัติการแปล",
"clearHistory": "ล้างประวัติ",
"clearHistoryConfirm":
"คุณแน่ใจหรือไม่ว่าต้องการล้างประวัติการแปลทั้งหมด? การกระทำนี้ไม่สามารถยกเลิกได้",
"translationHistory": "ประวัติการแปล",
// User Feedback Page
"feedback": "ข้อเสนอแนะ",
'feedbackInfo': 'ให้ข้อเสนอแนะหรือคำแนะนำ',
'feedbackDescription': 'โปรดอธิบายปัญหาหรือข้อเสนอแนะของคุณ',
'feedbackHint': 'กรุณากรอกข้อเสนอแนะของคุณ...',
'feedbackRequired': 'ต้องระบุเนื้อหาข้อเสนอแนะ',
'contactInfo': 'ข้อมูลติดต่อ',
'contactHint': 'อีเมล/โทรศัพท์ เพื่อให้เราสามารถติดต่อคุณได้',
'submit': 'ส่ง',
'feedbackSubmitSuccess': 'ส่งข้อเสนอแนะสำเร็จ ขอบคุณ!',
'feedbackSubmitFailed': 'ส่งข้อเสนอแนะล้มเหลว โปรดลองอีกครั้งภายหลัง',
// Language names (Ensure these match the keys used elsewhere if needed)
"中文(简体)": "จีน (ตัวย่อ)",
"中文(繁体)": "จีน (ตัวเต็ม)",
"中文(香港繁体)": "จีน (ฮ่องกง)",
"英语": "อังกฤษ",
"日语": "ญี่ปุ่น",
"韩语": "เกาหลี",
"法语": "ฝรั่งเศส",
"西班牙语": "สเปน",
"葡萄牙语": "โปรตุเกส",
"意大利语": "อิตาลี",
"德语": "เยอรมัน",
"俄语": "รัสเซีย",
"波兰语": "โปแลนด์",
"荷兰语": "ดัตช์",
"瑞典语": "สวีเดน",
"捷克语": "เช็ก",
"现代希腊语": "กรีกสมัยใหม่",
"罗马尼亚语": "โรมาเนีย",
"匈牙利语": "ฮังการี",
"乌克兰语": "ยูเครน",
"丹麦语": "เดนมาร์ก",
"芬兰语": "ฟินแลนด์",
"挪威语": "นอร์เวย์",
"克罗地亚语": "โครเอเชีย",
"加泰隆语": "คาตาลัน",
"阿拉伯语": "อาหรับ",
"泰语": "ไทย",
"越南语": "เวียดนาม",
"印尼语": "อินโดนีเซีย",
"印地语": "ฮินดี",
"土耳其语": "ตุรกี",
"马来语": "มาเลย์",
"孟加拉语": "เบงกาลี",
"泰米尔语": "ทมิฬ",
"泰卢固语": "เตลูกู",
"马拉提语": "มราฐี",
"乌尔都语": "อูรดู",
"波斯语": "เปอร์เซีย",
"斯瓦希里语": "สวาฮีลี",
};

427
lib/core/translations/language/tr_tr.dart

@ -0,0 +1,427 @@
const Map<String, String> trTR = {
// Genel (通用)
'continue': 'Devam Et',
'confirm': 'Onayla',
'cancel': 'İptal',
'retry': 'Tekrar Dene',
'error': 'Hata',
'success': 'Başarılı',
'connected': 'Bağlandı',
'connecting': 'Bağlanıyor...',
'connectionFailure': 'Bağlantı Hatası',
'unconnected': 'Bağlı Değil',
'search': 'Ara',
"requestCancellation": "İsteği İptal Et",
"connectionTimeout": "Bağlantı Zaman Aşımı",
"requestTimeout": "İstek Zaman Aşımı",
"responseTimeout": "Yanıt Zaman Aşımı",
"unknownError": "Bilinmeyen Hata",
// Başlatma Sayfası (启动页)
"slogan": "Dünyayla bağlantı kur, sesin ötesinde",
"sloganInfo": "Yapay Zeka Ses Uzmanı · Her Zaman, Her Yerde",
// Alt Navigasyon (底部导航)
"home": "Ana Sayfa",
"explore": "Keşfet",
"settings": "Ayarlar",
// Dil (语言)
"followingSystem": "Sistemi Takip Et",
"chinese": "Çince",
"english": "İngilizce",
"spanish": "İspanyolca",
"portuguese": "Portekizce",
"traditionalChinese": "Geleneksel Çince",
"japanese": "Japonca",
"korean": "Korece",
"french": "Fransızca",
"german": "Almanca",
"russian": "Rusça",
"vietnamese": "Vietnamca",
"italian": "İtalyanca",
"hindi": "Hintçe",
"indonesian": "Endonezce",
"thai": "Tayca",
"malay": "Malayca",
"turkish": "Türkçe",
"polish": "Lehçe",
// Giriş Sayfası (登录页)
"welcome": "Hoş Geldiniz",
"loginTips":
"Lütfen telefon numaranızı veya e-postanızı kullanarak giriş yapın veya hesap oluşturun",
"accountNumber": "Telefon Numarası veya E-posta",
"verificationCode": "Doğrulama Kodu",
"getCode": "Kodu Al",
"reacquireAfter": " saniye sonra tekrar al",
"otherWays": "Veya şununla giriş yapın",
"expressConsent": "Giriş yaparak/kaydolarak şunları kabul etmiş olursunuz:",
"and": " ve ",
"userAgreement": "Kullanıcı Sözleşmesi",
"privacyPolicy": "Gizlilik Politikası",
'selectCountry': 'Ülke/Bölge Seçin',
"email": "E-posta",
"phoneNumber": "Telefon Numarası",
"pleaseEnterContactInfo": "Lütfen telefon numarası veya e-posta girin",
"invalidEmailFormat": "Geçersiz e-posta formatı",
"invalidPhoneFormat": "Geçersiz telefon numarası formatı",
"tip": "İpucu",
"pleaseEnterCompleteCode": "Lütfen tam doğrulama kodunu girin",
// Giriş Hata İpuçları (登录错误提示)
"loginFailed": "Giriş başarısız, lütfen tekrar deneyin",
'pleaseAcceptPrivacyPolicy':
'Lütfen önce Gizlilik Politikasını ve Kullanıcı Sözleşmesini okuyup kabul edin',
// Ana Sayfa (首页)
"deviceStatus": "Cihaz Durumu",
"reconnect": "Yeniden Bağlan",
"connectingDevice": "Cihaza bağlanıyor...",
"connectedTo": "Bağlandı:",
"signal": "Sinyal",
"deviceId": "Cihaz Kimliği",
"firmwareVersion": "Donanım Yazılımı Sürümü",
"disconnectedDevice": "Bağlı cihaz yok, aşağıdaki düğmeye tıklayın",
"connectDevice": "Cihaz Bağla",
"topAiExperiences": "En İyi Yapay Zeka Deneyimleri",
"aiAssistant": "Yapay Zeka Asistanı",
"aiAssistantInfo": "Sorular ve tavsiyeler için 7/24 kişisel danışman",
"simultaneousInterpretation": "Eş Zamanlı Çeviri",
"simultaneousInterpretationInfo": "80+ dil için gerçek zamanlı çeviri",
"aiCompanion": "Ebeveyn-çocuk bağı",
"aiCompanionInfo": "Sizi anlar ve sıcak bir arkadaşlık sunar",
"homeSlogan": "Dünyayla bağlantı kur, sesin ötesinde",
"turnEarphones":
"Lütfen kulaklık kutusunu açın ve kulaklıkları kulaklarınıza takın",
"currentlyConnected": "Şu anda bağlı:",
"unsystemSetup": "Sistem ayarlarına git",
"talkLater": "Daha sonra hatırlat",
"initializing": "Başlatılıyor...",
"bluetoothInitializationFailure": "Bluetooth hizmeti başlatılamadı",
"checkingPermissions": "İzinler kontrol ediliyor...",
"lackPermissions": "Bluetooth izinleri gerekli",
"searchingDevice": "Cihaz aranıyor...",
"findConnectedDevice": "Bağlı cihaz bulundu",
"notFound": "Bağlı Bluetooth cihazı bulunamadı",
"initializationFailure": "Başlatma başarısız",
"connectedDevice": "Cihaza bağlandı:",
"failedRefreshDevice": "Cihaz yenilenemedi",
"noDeviceFound": "Bağlı cihaz bulunamadı",
"failedDeviceInformation": "Cihaz bilgileri alınamadı",
"unknownEquipment": "Bilinmeyen ekipman",
"zeroSignal": "Sinyal yok",
"good": "İyi",
"fine": "Harika",
"excellent": "Mükemmel",
"unknown": "Bilinmiyor",
'bluetoothTurnedOff': 'Bluetooth kapalı, lütfen açın',
'bluetoothOff': 'Bluetooth kapalı',
// Keşfet Sayfası (发现页)
"searchAi": "Yapay zeka özelliklerini ara...",
"exploreSlogan": "Akıllı Kulaklıklar, Sonsuz Olasılıklar",
"exploreSlogan1": "Akıllı arkadaş, Sonsuz Olasılıklar",
"exploreSlogan2": "Akıllı gözlükler, Sonsuz Olasılıklar",
"exploreAi":
"DeepSound tarafından sağlanan yapay zeka özelliklerini keşfedin",
"aiFunction": "Yapay Zeka Özellikleri",
"allRoundAssistant": "Çok Yönlü Asistan",
"allRoundAssistantInfo": "Çeşitli sorunları çözmeye yardımcı olur",
"simultaneousTranslation": "Gerçek Zamanlı Çeviri",
"simultaneousTranslationInfo": "80+ dil için anında çeviri",
"aiGirlfriend": "Ebeveyn-çocuk bağı",
"aiGirlfriendInfo": "Akıllı arkadaşlık, istediğiniz zaman sohbet edin",
"healthConsultant": "Çok fonksiyonlu AI sohbeti",
"healthConsultantInfo": "Daha fazla AI sohbet özelliği",
"conferenceAssistant": "İş Toplantısı Asistanı",
"conferenceAssistantInfo": "Gerçek zamanlı tutanak kaydı ve çok dilli çeviri",
"studyAssistant": "Dil Öğrenme Asistanı",
"studyAssistantInfo": "Telaffuz düzeltme ve konuşma pratiği",
"travelTranslator": "Seyahat Tercümanı",
"travelTranslatorInfo":
"Gerçek zamanlı sesli çeviri ve turistik yer rehberleri",
"beSearching": "Aranıyor:",
"noAssistantFound": "Karşılık gelen yapay zeka asistanı bulunamadı",
"scene": "Senaryolar",
"youHaveSelected": "Seçtiniz:",
"newFunction": "Yeni Özellikler",
"youChoseTry": "Denemeyi seçtiniz",
"aiSinger": "Yapay Zeka Şarkıcı Modu",
"aiSingerInfo":
"En sevdiğiniz sanatçının sesiyle herhangi bir şarkıyı söyleyin, hatta kendi bestelerinizi",
"popularFunction": "Popüler Özellikler",
"tryNow": "Şimdi Dene",
// İş Toplantısı Asistanı Sayfası (商务会议助手页)
"startMeeting": "Toplantıyı Başlat",
'edit': 'Düzenle',
'delete': 'Sil',
'noContent': 'İçerik Yok',
'editMeetingTitle': 'Toplantı Başlığını Düzenle (İsteğe Bağlı)',
'meetingTitle': 'Toplantı Başlığı (İsteğe Bağlı)',
'maxCharacters': 'Maksimum 50 karakter',
'meeting': 'Toplantı',
'recordingDetails': 'Kayıt Detayları',
'aiSummary': 'Yapay Zeka Özeti',
'meetingTranscript': 'Toplantı Transkripti',
// Ayarlar Sayfası (设置页)
"premiumMember": "Premium Üyelik",
"joinDevice": "Bağlı cihaz:",
"accountId": "Hesap Kimliği:",
"systemSetting": "Sistem Ayarları",
"languageSetting": "Dil Ayarları",
"darkMode": "Karanlık Mod",
"darkModeInfo": "Açık/karanlık temalar arasında geçiş yap",
"backups": "Yedekle ve Geri Yükle",
"backupsInfo": "Ayarları ve yapay zeka geçmişini yedekle",
"lastBackup": "Son yedekleme:",
"privacySettings": "Gizlilik Ayarları",
"privacySettingsInfo": "Veri toplama ve izinleri yönet",
"logout": "Oturumu Kapat",
"logoutTips": "Mevcut hesaptan çıkış yapmak istediğinizden emin misiniz?",
"aboutApplication": "Hakkında",
"versions": "Sürüm",
"checkUpdates": "Güncellemeleri Kontrol Et",
"selectLanguage": "Dil Seç",
"voiceService": "Ses Hizmeti",
"voiceServiceInfo": "Konuşma tanıma ve sentezleme hizmetini seçin",
"selectVoiceService": "Ses Hizmeti Seç",
"azureVoiceService": "Azure Konuşma Hizmeti",
"azureVoiceServiceInfo":
"Yüksek kaliteli konuşma sentezi ve tanımayı destekleyen Microsoft bulut tabanlı hizmet",
"flutterVoiceService": "Yerel ses hizmeti",
"flutterVoiceServiceInfo": "İnternet gerektirmeyen cihaz yerel ses hizmeti",
'timbreService': 'Ses Tonu Hizmeti',
'timbreServiceInfo': 'Ses sentezleme hizmetini seçin',
'defaultTimbre': 'Varsayılan Ses Tonu',
'microsoftCloudTimbreService': 'Microsoft Bulut Ses Tonu Hizmeti',
'voiceClone': 'Ses Klonlama',
'AIsettings': 'Yapay Zeka Ayarları',
'selecttimbre': 'Ses Tonu Seç',
'pleaseRead': 'Lütfen Okuyun',
'completeRecording': 'Kaydı Tamamla',
'deleteAccount': 'Hesabı Sil',
'deleteAccountTips':
'Hesabınızı kalıcı olarak silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.',
// Sohbet Sayfası (聊天页)
"clearChatHistory": "Sohbet Geçmişini Temizle",
"online": "Çevrimiçi",
"inputMessage": "Bir mesaj yazın...",
"imListening": "Dinliyorum...",
"canStartTalking": "Konuşmaya başlayabilirsiniz",
// Çeviri Sayfası (翻译页)
"selectSourceLanguage": "Kaynak Dil Seç",
"selectTargetLanguage": "Hedef Dil Seç",
// Profili Düzenle Sayfası (编辑个人信息页)
"editProfile": "Profili Düzenle",
"nickname": "Takma Ad",
"save": "Kaydet",
// Ülke Listesi (国家列表)
"邮箱": "E-posta", // Orijinal anahtarı koru
"中国": "Çin",
"美国": "Amerika Birleşik Devletleri",
"加拿大": "Kanada",
"俄罗斯": "Rusya",
"英国": "Birleşik Krallık",
"德国": "Almanya",
"意大利": "İtalya",
"法国": "Fransa",
"西班牙": "İspanya",
"南非": "Güney Afrika",
"沙特阿拉伯": "Suudi Arabistan",
"土耳其": "Türkiye",
"日本": "Japonya",
"韩国": "Güney Kore",
"新加坡": "Singapur",
"马来西亚": "Malezya",
"泰国": "Tayland",
"越南": "Vietnam",
"印度": "Hindistan",
"巴基斯坦": "Pakistan",
"菲律宾": "Filipinler",
"印度尼西亚": "Endonezya",
"阿联酋": "Birleşik Arap Emirlikleri",
"墨西哥": "Meksika",
"巴西": "Brezilya",
"阿根廷": "Arjantin",
"澳大利亚": "Avustralya",
"新西兰": "Yeni Zelanda",
"中国香港": "Hong Kong, Çin",
"中国澳门": "Makao, Çin",
"中国台湾": "Tayvan, Çin",
"埃及": "Mısır",
"希腊": "Yunanistan",
"荷兰": "Hollanda",
"比利时": "Belçika",
"匈牙利": "Macaristan",
"罗马尼亚": "Romanya",
"瑞士": "İsviçre",
"奥地利": "Avusturya",
"丹麦": "Danimarka",
"瑞典": "İsveç",
"挪威": "Norveç",
"波兰": "Polonya",
"秘鲁": "Peru",
"智利": "Şili",
"哥伦比亚": "Kolombiya",
"委内瑞拉": "Venezuela",
"葡萄牙": "Portekiz",
"卢森堡": "Lüksemburg",
"爱尔兰": "İrlanda",
"冰岛": "İzlanda",
"阿尔巴尼亚": "Arnavutluk",
"马耳他": "Malta",
"塞浦路斯": "Kıbrıs",
"芬兰": "Finlandiya",
"保加利亚": "Bulgaristan",
"立陶宛": "Litvanya",
"拉脱维亚": "Letonya",
"爱沙尼亚": "Estonya",
"乌克兰": "Ukrayna",
"塞尔维亚": "Sırbistan",
"克罗地亚": "Hırvatistan",
"斯洛文尼亚": "Slovenya",
"捷克": "Çek Cumhuriyeti",
"斯洛伐克": "Slovakya",
"危地马拉": "Guatemala",
"萨尔瓦多": "El Salvador",
"洪都拉斯": "Honduras",
"尼加拉瓜": "Nikaragua",
"哥斯达黎加": "Kosta Rika",
"巴拿马": "Panama",
"海地": "Haiti",
"玻利维亚": "Bolivya",
"厄瓜多尔": "Ekvador",
"巴拉圭": "Paraguay",
"乌拉圭": "Uruguay",
"摩洛哥": "Fas",
"阿尔及利亚": "Cezayir",
"突尼斯": "Tunus",
"尼日利亚": "Nijerya",
"加纳": "Gana",
"埃塞俄比亚": "Etiyopya",
"肯尼亚": "Kenya",
"坦桑尼亚": "Tanzanya",
"乌干达": "Uganda",
"孟加拉国": "Bangladeş",
"以色列": "İsrail",
"黎巴嫩": "Lübnan",
"约旦": "Ürdün",
"伊拉克": "Irak",
"科威特": "Kuveyt",
"巴林": "Bahreyn",
"卡塔尔": "Katar",
"阿曼": "Umman",
"阿富汗": "Afganistan",
"斯里兰卡": "Sri Lanka",
"缅甸": "Myanmar",
"尼泊尔": "Nepal",
"蒙古": "Moğolistan",
"柬埔寨": "Kamboçya",
"老挝": "Laos",
"文莱": "Brunei",
"东帝汶": "Doğu Timor",
"斐济": "Fiji",
"所罗门群岛": "Solomon Adaları",
// Kişisel Asistan Emma (Personal Assistant Emma)
"personalAssistantName": "Emma",
"personalAssistantDescription":
"Merhaba, ben Emma, çok yönlü bir yapay zeka asistanıyım. İş planlaması, çalışma tavsiyesi, yaşam düzenlemeleri veya başka herhangi bir konuda size yardımcı olmaktan mutluluk duyarım. Birlikte verimliliği artıralım ve hedeflere ulaşalım!",
"assistantTag": "Asistan",
"personalAssistantWelcome":
"Merhaba! Ben Emma ve kişisel asistanınız olmaktan mutluluk duyuyorum. Zaman yönetimi, hedef planlama, bilgi düzenleme vb. gibi çeşitli görevlerde size yardımcı olabilirim. Size nasıl yardımcı olabilirim?",
"personalAssistantVoiceWelcome": "Buradayım!",
// Eş Zamanlı Tercüman Lucy (同声传译 Lucy)
"interpreterName": "Eş Zamanlı Tercüman",
"interpreterDescription":
"Merhaba! Ben Lucy, toplantılar, dersler ve konuşmalar için gerçek zamanlı çeviri konusunda uzmanlaşmış profesyonel bir İngilizce-Çince tercümanım, iletişimi engelsiz hale getiriyorum.",
"interpreterTag": "Çeviri",
"interpreterWelcome":
"Herkese merhaba! Ben Lucy, eş zamanlı tercümanınız. Sizin için Çince ve İngilizce arasında çeviri yapacağım. Çince konuştuğunuzda İngilizceye çevireceğim; İngilizce konuştuğunuzda Çinceye çevireceğim. Çeviri oturumumuza başlayalım!",
// Ebeveyn-Çocuk Arkadaşı XinYuan (亲子陪伴 芯愿)
"companionName": "XinYuan",
"companionDescription":
"Ben XinYuan, özel ebeveyn-çocuk arkadaşınız, nazik ve anlayışlıyım. Sohbet etmek, endişelerinizi paylaşmak veya arkadaşlığa ihtiyaç duymak isteyin, her zaman yanınızda olacağım.",
"companionTag": "Duygusal",
"companionWelcome":
"Merhaba! Sonunda sizinle tanıştım! Ben XinYuan, özel ebeveyn-çocuk arkadaşınız. Gününüz nasıldı? Benimle paylaşmak istediğiniz bir şey var mı? Her zaman buradayım, düşüncelerinizi dinlemeye hazırım.",
"companionVoiceWelcome": "Seninle buradayım!",
// Çok Fonksiyonlu Yapay Zeka Sohbeti Wu (多功能AI聊天 吴小江)
"healthAdvisorName": "Yapay Zeka Sohbet Asistanı Wu",
"healthAdvisorDescription":
"Ben Wu, size çeşitli yapay zeka sohbet özellikleri sunmaya hazırım.",
"healthAdvisorTag": "Sohbet",
"healthAdvisorWelcome":
"Merhaba, ben Wu. Yapay zeka sohbet özellikleriyle size yardımcı olmaktan mutluluk duyarım. Bugün size nasıl yardımcı olabilirim?",
"healthAdvisorVoiceWelcome": "Buradayım!",
// Çeviri Geçmişi (Translation History)
"noTranslationHistory": "Çeviri geçmişi yok",
"clearHistory": "Geçmişi Temizle",
"clearHistoryConfirm":
"Tüm çeviri geçmişini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"translationHistory": "Çeviri Geçmişi",
// Kullanıcı Geri Bildirim Sayfası (User Feedback Page)
"feedback": "Geri Bildirim",
'feedbackInfo': 'Geri bildirim veya öneri sağlayın',
'feedbackDescription': 'Lütfen sorununuzu veya önerinizi açıklayın',
'feedbackHint': 'Lütfen geri bildiriminizi girin...',
'feedbackRequired': 'Geri bildirim içeriği gerekli',
'contactInfo': 'İletişim Bilgileri',
'contactHint': 'E-posta/Telefon, böylece sizinle iletişime geçebiliriz',
'submit': 'Gönder',
'feedbackSubmitSuccess': 'Geri bildirim başarıyla gönderildi, teşekkürler!',
'feedbackSubmitFailed':
'Geri bildirim gönderilemedi, lütfen daha sonra tekrar deneyin',
// Dil Adları (Language names)
"中文(简体)": "Çince (Basitleştirilmiş)",
"中文(繁体)": "Çince (Geleneksel)",
"中文(香港繁体)": "Çince (Hong Kong)",
"英语": "İngilizce",
"日语": "Japonca",
"韩语": "Korece",
"法语": "Fransızca",
"西班牙语": "İspanyolca",
"葡萄牙语": "Portekizce",
"意大利语": "İtalyanca",
"德语": "Almanca",
"俄语": "Rusça",
"波兰语": "Lehçe",
"荷兰语": "Hollandaca",
"瑞典语": "İsveççe",
"捷克语": "Çekçe",
"现代希腊语": "Modern Yunanca",
"罗马尼亚语": "Rumence",
"匈牙利语": "Macarca",
"乌克兰语": "Ukraynaca",
"丹麦语": "Danca",
"芬兰语": "Fince",
"挪威语": "Norveççe",
"克罗地亚语": "Hırvatça",
"加泰隆语": "Katalanca",
"阿拉伯语": "Arapça",
"泰语": "Tayca",
"越南语": "Vietnamca",
"印尼语": "Endonezce",
"印地语": "Hintçe",
"土耳其语": "Türkçe",
"马来语": "Malayca",
"孟加拉语": "Bengalce",
"泰米尔语": "Tamilce",
"泰卢固语": "Teluguca",
"马拉提语": "Marathice",
"乌尔都语": "Urduca",
"波斯语": "Farsça",
"斯瓦希里语": "Svahili",
};

6
lib/core/translations/language/vi_vn.dart

@ -40,6 +40,12 @@ const Map<String, String> viVN = {
"russian": "Tiếng Nga",
"vietnamese": "Tiếng Việt",
"italian": "Tiếng Ý",
"hindi": "Tiếng Hindi",
"indonesian": "Tiếng Indonesia",
"thai": "Tiếng Thái",
"malay": "Tiếng Mã Lai",
"turkish": "Tiếng Thổ Nhĩ Kỳ",
"polish": "Tiếng Ba Lan",
// 登录页
"welcome": "Chào mừng",

6
lib/core/translations/language/zh_cn.dart

@ -40,6 +40,12 @@ const Map<String, String> zhCN = {
"russian": "俄语",
"vietnamese": "越南语",
"italian": "意大利语",
"hindi": "印地语",
"indonesian": "印度尼西亚语",
"thai": "泰语",
"malay": "马来语",
"turkish": "土耳其语",
"polish": "波兰语",
// 登录页
"welcome": "欢迎使用",

6
lib/core/translations/language/zh_hk.dart

@ -40,6 +40,12 @@ const Map<String, String> zhHK = {
"russian": "俄語",
"vietnamese": "越南語",
"italian": "意大利語",
"hindi": "印地語",
"indonesian": "印尼語",
"thai": "泰語",
"malay": "馬來語",
"turkish": "土耳其語",
"polish": "波蘭語",
// 登录页
"welcome": "歡迎使用",

4
lib/data/models/appconfig.dart

@ -21,8 +21,8 @@ class AppConfig {
}
//智能体配置
static String agent(String agentId) {
static String? agentsystemPrompt(String agentId) {
final foundAgent = config.agents.firstWhere((agent) => agent.id == agentId);
return jsonEncode((foundAgent));
return foundAgent.systemPrompt;
}
}

4
lib/data/services/asr_service.dart

@ -19,7 +19,9 @@ abstract class AsrService {
/// 开始连续语音识别
///
/// 返回一个包含识别事件的流
Future<Stream<RecognitionEvent>> startContinuousRecognition();
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType,
);
/// 停止连续语音识别
Future<bool> stopContinuousRecognition();

13
lib/data/services/ble_manager.dart

@ -404,6 +404,8 @@ class BleManager extends GetxService {
final openaiBaseUrl =
AppConfig.env('OPENAI_BASE_URL') ?? 'https://api.openai.com/v1';
final openaiModel = AppConfig.env('OPENAI_MODEL') ?? 'gpt-3.5-turbo';
final systemPrompt = AppConfig.agentsystemPrompt("personal_assistant");
// 从Flutter资源包中加载.mcp.json
final jsonData = AppConfig.mcpConfig();
@ -414,6 +416,7 @@ class BleManager extends GetxService {
'openaiApiKey': openaiApiKey,
'openaiBaseUrl': openaiBaseUrl,
'openaiModel': openaiModel,
'systemPrompt': systemPrompt,
'mcpServer': jsonEncode(jsonData),
};
@ -534,6 +537,16 @@ class BleManager extends GetxService {
}
}
/// 打开解码器
Future<bool> openA2DPDecoder() async {
try {
return await _bleService.openA2DPDecoder();
} catch (e) {
Logger.error('打开解码器失败: $e');
return false;
}
}
/// 关闭编解码器
Future<bool> closeCodec() async {
try {

44
lib/data/services/music_manager.dart

@ -0,0 +1,44 @@
import 'package:deep_voice/core/utils/logger.dart';
import 'package:get/get.dart';
import 'package:music_service/music_service.dart';
//音乐播放管理
class MusicManager extends GetxService {
static MusicManager get to => Get.find<MusicManager>();
// 音乐服务实例
final MusicService _musicService = MusicService();
/// 初始化
@override
void onInit() {
super.onInit();
Logger.info('BLE管理器初始化');
}
/// 释放资源
@override
void onClose() {
super.onClose();
}
//播放
Future<void> play(String url) async {
await _musicService.playMusic(url);
}
//暂停
Future<void> pause() async {
await _musicService.pauseMusic();
}
//继续
Future<void> resume() async {
await _musicService.resumeMusic();
}
//状态
Future<Map<String, dynamic>> getStatus() async {
return await _musicService.getStatus();
}
}

9
lib/data/services/network/api.dart

@ -73,4 +73,13 @@ class Api {
params: params,
);
}
//app获取设备信息
static getequipmentinfo([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_getequipmentinfo',
params: params,
);
}
}

7
lib/data/services/speech_impl/azure_asr_service.dart

@ -150,7 +150,8 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}
@ -164,7 +165,9 @@ class AzureAsrService extends GetxService implements AsrService {
// 开始连续识别
final bool result =
await _channel.invokeMethod('startContinuousRecognition');
await _channel.invokeMethod('startContinuousRecognition', {
'audioSourceType': audioSourceType,
});
if (!result) {
_cleanupEventStream();

3
lib/data/services/speech_impl/flutter_asr_service.dart

@ -363,7 +363,8 @@ class FlutterAsrService extends GetxService implements AsrService {
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
try {
if (!_isAvailable.value) {
final controller = StreamController<RecognitionEvent>.broadcast();

8
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -36,7 +36,8 @@ class VolcanoAsrApiService implements AsrService {
final int _bitsPerSample = 16;
final int _channels = 1;
final String _encoding = 'pcm';
// 音频输入源
bool _audioSourceType = false;
// 连接相关
WebSocketChannel? _channel;
Completer<void>? _connectionCompleter;
@ -113,7 +114,7 @@ class VolcanoAsrApiService implements AsrService {
final resultCompleter = Completer<RecognitionEvent>();
// 开始连续识别流程
final stream = await startContinuousRecognition();
final stream = await startContinuousRecognition(_audioSourceType);
// 监听结果
StreamSubscription? subscription;
@ -156,7 +157,8 @@ class VolcanoAsrApiService implements AsrService {
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}

10
lib/data/services/speech_impl/volcano_asr_service.dart

@ -265,7 +265,8 @@ class VolcanoAsrService extends GetxService implements AsrService {
/// 开始连续语音识别
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
if (!await _ensureInitialized()) {
_eventStreamController.add(RecognitionEvent(
type: RecognitionEventType.error,
@ -275,9 +276,10 @@ class VolcanoAsrService extends GetxService implements AsrService {
}
try {
final success =
await _channel.invokeMethod<bool>('startContinuousRecognition') ??
false;
final success = await _channel.invokeMethod<bool>('audioSourceType', {
'audioSourceType': audioSourceType,
}) ??
false;
if (success) {
_isContinuousRecognitionActive = true;

3
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -75,7 +75,8 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}

207
lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart

@ -9,8 +9,8 @@ import '../../models/events/voice_interaction_event.dart';
import '../../../core/utils/logger.dart';
enum VoiceInteractionState {
idle, // 等待唤醒
active, // 活跃状态 - 可以同时识别和播放
idle, // 等待唤醒
active, // 活跃状态 - 可以同时识别和播放
}
class IosVoiceInteractionService extends GetxService {
@ -18,45 +18,47 @@ class IosVoiceInteractionService extends GetxService {
final TtsService _ttsService = Get.find<TtsService>();
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
final ChatHistoryService _chatHistoryService = Get.find<ChatHistoryService>();
// 当前状态
final Rx<VoiceInteractionState> state = VoiceInteractionState.idle.obs;
// 是否识别到用户语音
final RxBool isSpeechDetected = false.obs;
// 是否正在处理AI响应
final RxBool isProcessingAI = false.obs;
// 是否正在播放TTS
final RxBool isSpeaking = false.obs;
// 对话历史记录
final List<Map<String, String>> _messageHistory = [];
// 最大历史记录数
static const int _maxHistorySize = 10;
// 超时计时器
Timer? _inactivityTimer;
// 流订阅
StreamSubscription? _recognitionSubscription;
// 当前识别的文本
String _currentRecognizedText = '';
// 音频输入源
bool _audioSourceType = false;
// 事件流控制器
final StreamController<VoiceInteractionEvent> _eventStreamController =
final StreamController<VoiceInteractionEvent> _eventStreamController =
StreamController<VoiceInteractionEvent>.broadcast();
// 获取事件流
@override
Stream<VoiceInteractionEvent> get eventStream => _eventStreamController.stream;
Stream<VoiceInteractionEvent> get eventStream =>
_eventStreamController.stream;
// 标识符,用于存储聊天历史
final String _agentId = 'ios_assistant';
/// 初始化服务
@override
Future<IosVoiceInteractionService> initialize() async {
@ -66,10 +68,10 @@ class IosVoiceInteractionService extends GetxService {
} catch (e) {
Logger.error('iOS语音交互服务初始化失败: $e');
}
return this;
}
// 处理唤醒事件
void onWakeup() {
_ttsService.speakOnce('我在呢!');
@ -79,63 +81,61 @@ class IosVoiceInteractionService extends GetxService {
_activateInteraction();
}
}
// 激活交互
void _activateInteraction() {
// 设置状态为活跃
state.value = VoiceInteractionState.active;
// 开始连续监听
_startContinuousListening();
// 启动不活动计时器
_startInactivityTimer();
// 发送识别开始事件
_sendRecognitionStartedEvent();
}
// 开始连续监听
Future<void> _startContinuousListening() async {
// 停止之前的监听
_recognitionSubscription?.cancel();
try {
// 开始连续识别
final recognitionStream = await _asrService.startContinuousRecognition();
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
// 监听识别结果
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,
onError: (error) {
Logger.error('语音识别错误: $error');
_startContinuousListening(); // 尝试重新启动
}
);
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent, onError: (error) {
Logger.error('语音识别错误: $error');
_startContinuousListening(); // 尝试重新启动
});
Logger.info('开始连续语音识别');
} catch (e) {
Logger.error('启动语音识别失败: $e');
}
}
// 处理识别事件
void _handleRecognitionEvent(RecognitionEvent event) {
// 更新活动时间
_updateActivityTime();
switch (event.type) {
case RecognitionEventType.intermediateResult:
// 检测用户是否开始说话
if (event.text.isNotEmpty && !isSpeechDetected.value) {
isSpeechDetected.value = true;
// 用户开始讲话时,立即中断当前响应
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
}
break;
case RecognitionEventType.finalResult:
if (event.text.isNotEmpty) {
// 最终结果,处理用户输入
@ -144,16 +144,16 @@ class IosVoiceInteractionService extends GetxService {
// 重置语音检测状态
isSpeechDetected.value = false;
break;
case RecognitionEventType.sessionStarted:
Logger.info('语音识别会话开始');
break;
case RecognitionEventType.sessionStopped:
Logger.info('语音识别会话结束');
break;
case RecognitionEventType.error:
case RecognitionEventType.canceled:
Logger.error('语音识别错误: ${event.error}');
@ -161,11 +161,11 @@ class IosVoiceInteractionService extends GetxService {
break;
}
}
// 中断当前响应
void _interruptCurrentResponse(String reason) {
Logger.info(reason);
// 如果正在活跃状态,需要中断当前操作
if (state.value == VoiceInteractionState.active) {
// 停止TTS播放
@ -174,40 +174,40 @@ class IosVoiceInteractionService extends GetxService {
isProcessingAI.value = false;
}
}
// 处理用户输入
Future<void> _processUserInput(String text) async {
Logger.info('处理用户输入: $text');
// 设置为处理状态
isProcessingAI.value = true;
// 添加用户消息到历史记录
final userMessage = {'role': 'user', 'content': text};
_addToHistory(userMessage);
// 创建用户消息对象
final userMsg = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
// AI响应处理标志
bool isProcessingCancelled = false;
try {
// 构建消息历史
final messageHistory = _buildMessageHistory();
// 调用AI服务获取响应
final responseStream = _aiService.sendMessageStream(
messages: messageHistory,
systemPrompt: "你是一个智能助手,请简明扼要地回答问题。",
);
String aiResponse = '';
// 等待AI响应
await for (final chunk in responseStream) {
// 检查是否被用户打断
@ -217,49 +217,49 @@ class IosVoiceInteractionService extends GetxService {
Logger.info('AI响应生成过程中被用户打断');
break;
}
// 如果状态已改变(可能由其他原因导致),停止处理
if (state.value != VoiceInteractionState.active) {
isProcessingCancelled = true;
break;
}
aiResponse += chunk;
// 立即播放当前文本块,实现边生成边播放
_speakStreamResponse(chunk);
}
// 如果处理被取消,不继续后续操作
if (isProcessingCancelled) {
isProcessingAI.value = false;
return;
}
// 确保完整响应被处理
if (aiResponse.isNotEmpty) {
// 将AI响应添加到历史记录
final assistantMessage = {'role': 'assistant', 'content': aiResponse};
_addToHistory(assistantMessage);
// 创建助手消息对象
final assistantMsg = Message(
role: 'assistant',
content: aiResponse,
timestamp: DateTime.now(),
);
// 保存聊天历史
await _saveChatHistory(userMsg, assistantMsg);
// 发送聊天历史事件
_sendChatHistoryEvent(userMsg.content, assistantMsg.content);
// 刷新TTS流,确保所有文本都被播放
await _ttsService.flushStream();
isSpeaking.value = false;
}
// 处理完成
isProcessingAI.value = false;
} catch (e) {
@ -268,59 +268,59 @@ class IosVoiceInteractionService extends GetxService {
isProcessingAI.value = false;
}
}
// 构建用于AI服务的消息历史
List<Map<String, String>> _buildMessageHistory() {
return List<Map<String, String>>.from(_messageHistory);
}
// 添加消息到历史记录
void _addToHistory(Map<String, String> message) {
_messageHistory.add(message);
// 限制历史记录大小
while (_messageHistory.length > _maxHistorySize) {
_messageHistory.removeAt(0);
}
}
// 播放流式响应
void _speakStreamResponse(String chunk) {
if (chunk.isEmpty) return;
// 更新播放状态
isSpeaking.value = true;
// 使用流式TTS播放
_ttsService.speakStream(chunk);
// 更新活动时间
_updateActivityTime();
}
// 停止TTS
void _stopTts() {
_ttsService.stop();
isSpeaking.value = false;
}
// 更新最后活动时间
DateTime _lastActivityTime = DateTime.now();
void _updateActivityTime() {
_lastActivityTime = DateTime.now();
}
// 启动不活动计时器(长时间无交互会切换到空闲状态)
void _startInactivityTimer() {
_cancelInactivityTimer();
_inactivityTimer = Timer.periodic(Duration(seconds: 5), (timer) {
// 计算空闲时间
final idleTime = DateTime.now().difference(_lastActivityTime).inSeconds;
// 如果空闲超过30秒,且不在播放或检测到语音,切换到空闲
if (idleTime > 8 &&
!isSpeaking.value &&
if (idleTime > 8 &&
!isSpeaking.value &&
!isProcessingAI.value &&
!isSpeechDetected.value) {
_resetToIdle();
@ -328,37 +328,37 @@ class IosVoiceInteractionService extends GetxService {
}
});
}
// 取消不活动计时器
void _cancelInactivityTimer() {
_inactivityTimer?.cancel();
_inactivityTimer = null;
}
// 重置到空闲状态
void _resetToIdle() {
_ttsService.speakOnce('没有听到声音,暂停对话,双击耳机唤醒!');
_cancelInactivityTimer();
// 停止TTS
_stopTts();
// 停止语音识别
_recognitionSubscription?.cancel();
_asrService.stopContinuousRecognition();
// 重置状态
state.value = VoiceInteractionState.idle;
isSpeechDetected.value = false;
isProcessingAI.value = false;
isSpeaking.value = false;
// 清空历史记录
_messageHistory.clear();
Logger.info('语音交互已重置为空闲状态');
}
// 暂停语音交互
@override
void pauseVoiceInteraction() {
@ -366,53 +366,53 @@ class IosVoiceInteractionService extends GetxService {
_stopTts();
_recognitionSubscription?.cancel();
_asrService.stopContinuousRecognition();
// 重置所有状态
state.value = VoiceInteractionState.idle;
isSpeechDetected.value = false;
isProcessingAI.value = false;
isSpeaking.value = false;
// 清空历史记录
_messageHistory.clear();
}
}
// 恢复语音交互
void resumeVoiceInteraction() {
if (state.value == VoiceInteractionState.idle) {
_activateInteraction();
}
}
// 手动停止交互
void stopInteraction() {
_resetToIdle();
}
// 获取当前状态
VoiceInteractionState getCurrentState() {
return state.value;
}
// 是否处于活跃状态
@override
bool isActive() {
return state.value != VoiceInteractionState.idle;
}
// 获取当前对话历史
@override
List<Map<String, String>> getMessageHistory() {
return List<Map<String, String>>.from(_messageHistory);
}
// 获取当前服务ID
@override
String getServiceId() {
return _agentId;
}
// 释放资源
@override
void onClose() {
@ -422,25 +422,25 @@ class IosVoiceInteractionService extends GetxService {
_eventStreamController.close();
super.onClose();
}
// 保存聊天历史
Future<void> _saveChatHistory(Message userMsg, Message assistantMsg) async {
try {
// 加载现有历史记录
final existingMessages = await _chatHistoryService.loadHistory(_agentId);
// 添加新消息
existingMessages.addAll([userMsg, assistantMsg]);
// 保存更新后的历史记录
await _chatHistoryService.saveHistory(_agentId, existingMessages);
Logger.info('保存聊天历史记录成功');
} catch (e) {
Logger.error('保存聊天历史记录失败: $e');
}
}
// 发送聊天历史事件
void _sendChatHistoryEvent(String userMessage, String assistantMessage) {
final event = ChatHistoryEvent(
@ -449,19 +449,18 @@ class IosVoiceInteractionService extends GetxService {
assistantMessage: assistantMessage,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(event);
Logger.info('已发送聊天历史事件');
}
// 发送识别开始事件
void _sendRecognitionStartedEvent() {
final event = RecognitionStartedEvent(
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(event);
Logger.info('已发送识别开始事件');
}
}

246
lib/modules/agent/controllers/agent_controller.dart

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:deep_voice/data/services/music_manager.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:agent_service/agent_service.dart';
@ -15,6 +16,7 @@ class Message {
final bool isRecognizing; // 是否为语音识别中的临时消息
final bool hasImage; // 是否包含图片
final String? imagePath; // 图片路径
final Map? meta; //卡片数据
Message({
required this.isUser,
@ -23,50 +25,56 @@ class Message {
this.isRecognizing = false,
this.hasImage = false,
this.imagePath,
this.meta,
}) : this.timestamp = timestamp ?? DateTime.now();
}
class AgentController extends GetxController {
static AgentController get to => Get.find();
final logger = Logger();
// 聊天消息列表
final messages = <Message>[].obs;
// 控制器
final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController();
//音乐播放管理
final _musiceManager = Get.find<MusicManager>();
var isPlaying = false.obs;
var currentUrl = "".obs;
// 状态标志
final isInitialized = false.obs;
final isListening = false.obs;
final isSpeaking = false.obs;
final isProcessing = false.obs;
// 图片处理状态
final isImageProcessing = false.obs;
// 输入模式控制
final isTextInputMode = true.obs;
// 当前输入的文本内容
final currentText = ''.obs;
// 图片输入相关
final isImageInputActive = false.obs; // 是否处于图片输入模式
final selectedImagePath = Rx<String?>(null); // 当前选择的图片路径
// 流事件订阅
StreamSubscription<AgentServiceEvent>? _eventSubscription;
// 用于跟踪AI回复状态
bool _isNewAssistantResponse = true;
String _currentResponseId = '';
// 会话ID
final String sessionId = 'default_agent';
// 是否已加载聊天记录
final isHistoryLoaded = false.obs;
@ -86,7 +94,7 @@ class AgentController extends GetxController {
_eventSubscription?.cancel();
super.onClose();
}
// 加载聊天历史记录
Future<void> _loadChatHistory() async {
try {
@ -96,19 +104,25 @@ class AgentController extends GetxController {
page: 1,
pageSize: 20,
);
if (result.messages.isNotEmpty) {
// 转换并添加消息到列表
final historyMessages = result.messages.map((msg) => Message(
isUser: msg.sender == 'user',
text: msg.message,
timestamp: DateTime.parse(msg.timestamp),
)).toList();
final historyMessages = result.messages.map((msg) {
final meta = msg.metadata != null && msg.metadata != ""
? jsonDecode(msg.metadata!)
: null; // 如果为空,使用空对象
return Message(
isUser: msg.sender == 'user',
text: msg.message,
meta: meta,
timestamp: DateTime.parse(msg.timestamp),
);
}).toList();
// 消息按时间排序,最新的消息在底部
historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
messages.assignAll(historyMessages);
logger.i('成功加载 ${historyMessages.length} 条聊天记录');
} else {
// 没有历史记录时添加欢迎消息
@ -120,16 +134,15 @@ class AgentController extends GetxController {
_addWelcomeMessage();
} finally {
isHistoryLoaded.value = true;
messages.refresh();
// 在加载完成后直接定位到底部,无需动画
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
});
}
}
// 添加欢迎消息
void _addWelcomeMessage() {
messages.add(Message(
@ -159,15 +172,16 @@ class AgentController extends GetxController {
isTextInputMode.value = true;
isTextInputMode.refresh();
break;
case AgentServiceEventType.recognizing:
final text = event.data['text'] ?? '';
currentText.value = text; // 保留当前文本,以便其他地方使用
if (text.isNotEmpty) {
// 查找是否有正在识别中的消息
final index = messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
final index = messages
.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (index >= 0) {
// 更新已有的识别中消息
messages[index] = Message(
@ -188,13 +202,14 @@ class AgentController extends GetxController {
_scrollToBottom();
}
break;
case AgentServiceEventType.recognitionResult:
final text = event.data['text'] ?? '';
// 查找是否有正在识别中的消息
final index = messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (text.isNotEmpty) {
if (index >= 0) {
// 将识别中的消息转换为最终消息
@ -208,7 +223,7 @@ class AgentController extends GetxController {
// 如果没有找到识别中的消息,添加新消息
messages.add(Message(isUser: true, text: text));
}
isProcessing.value = true;
// 标记为新的AI回复
_isNewAssistantResponse = true;
@ -220,11 +235,11 @@ class AgentController extends GetxController {
messages.refresh();
}
break;
case AgentServiceEventType.ttsStarted:
isSpeaking.value = true;
break;
case AgentServiceEventType.ttsCompleted:
case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled:
@ -238,16 +253,17 @@ class AgentController extends GetxController {
case AgentServiceEventType.imageReady:
// 图片准备就绪,但AI还没有开始处理,保持processing状态
break;
case AgentServiceEventType.assistantToken:
if (!isProcessing.value) isProcessing.value = true;
final token = event.data['token'] ?? '';
final responseId = event.data['responseId'] ?? '';
if (token.isNotEmpty) {
// 如果是新的回复或者响应ID改变,创建新消息
if (_isNewAssistantResponse || (_currentResponseId != responseId && responseId.isNotEmpty)) {
if (_isNewAssistantResponse ||
(_currentResponseId != responseId && responseId.isNotEmpty)) {
messages.add(Message(isUser: false, text: token));
_isNewAssistantResponse = false;
_currentResponseId = responseId;
@ -259,6 +275,7 @@ class AgentController extends GetxController {
messages[lastIndex] = Message(
isUser: false,
text: messages[lastIndex].text + token,
meta: messages[lastIndex].meta,
timestamp: messages[lastIndex].timestamp,
);
}
@ -267,35 +284,37 @@ class AgentController extends GetxController {
_scrollToBottom();
}
break;
case AgentServiceEventType.assistantResponse:
isProcessing.value = false;
isImageProcessing.value = false;
// 标记当前回复完成
_isNewAssistantResponse = true;
break;
case AgentServiceEventType.error:
isListening.value = false;
isSpeaking.value = false;
isProcessing.value = false;
isImageProcessing.value = false;
// 移除临时的识别消息
final index = messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (index >= 0) {
messages.removeAt(index);
messages.refresh();
}
logger.e('代理服务错误: ${event.data['message']}');
break;
case AgentServiceEventType.autoStop:
isListening.value = false;
// 移除临时的识别消息
final index = messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (index >= 0) {
// 只有当消息为空或文本太短时才移除
if (messages[index].text.trim().length < 2) {
@ -309,26 +328,56 @@ class AgentController extends GetxController {
timestamp: messages[index].timestamp,
);
messages.refresh();
// 发送此消息到AI处理
isProcessing.value = true;
_isNewAssistantResponse = true;
AgentService.processTextInput(messages[index].text, speakResponse: true);
AgentService.processTextInput(messages[index].text,
speakResponse: true);
}
}
break;
case AgentServiceEventType.functionCall:
// final functionName = event.data['name'] ?? '';
// final arguments = event.data['arguments'] ?? '';
// logger.i('mcp: $functionName, $arguments');
final functionName = event.data['name'] ?? '';
final arguments = event.data['arguments'] ?? '';
logger.i('Fultter 开始调用 mcp: $functionName, $arguments');
break;
case AgentServiceEventType.functionCallResult:
final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? '';
final functionCall = event.data['function_call'] ?? '';
logger.i('Flutter 结束调用 mcp: $result, $functionCall');
if (result.isNotEmpty) {
final parsedResult = jsonDecode(result);
final meta = parsedResult["meta"] ?? "";
final metaResult = jsonDecode(meta);
logger.i('Flutter 结束调用 mcp metaResult: $metaResult');
// 判断是否为新的回复或响应ID是否改变
if (_isNewAssistantResponse || _currentResponseId != responseId) {
// 添加新消息
messages.add(Message(isUser: false, text: "", meta: metaResult));
_isNewAssistantResponse = false;
_currentResponseId = responseId;
} else {
// 更新最后一条消息
final lastIndex = messages.length - 1;
if (lastIndex >= 0 && !messages[lastIndex].isUser) {
messages[lastIndex] = Message(
isUser: false,
text: messages[lastIndex].text,
meta: meta,
timestamp: messages[lastIndex].timestamp,
);
}
}
// 滚动到底部
_scrollToBottom();
}
break;
default:
break;
}
@ -359,22 +408,22 @@ class AgentController extends GetxController {
final text = textController.text.trim();
final imagePath = selectedImagePath.value!; // 使用!强制断言非空
await sendImageMessage(imagePath, text: text);
// 清除图片输入状态
clearImageInput();
return;
}
final text = textController.text.trim();
if (text.isEmpty) return;
final message = Message(isUser: true, text: text);
messages.add(message);
textController.clear();
// 滚动到底部
_scrollToBottom();
try {
isProcessing.value = true;
// 标记为新的AI回复
@ -392,31 +441,31 @@ class AgentController extends GetxController {
logger.e('图片不存在: $imagePath');
return;
}
// 显示的文本,如果没有提供则使用默认值
final displayText = text?.isNotEmpty == true ? text! : '[图片]';
// 添加用户消息
final message = Message(
isUser: true,
isUser: true,
text: displayText,
hasImage: true,
imagePath: imagePath,
);
messages.add(message);
// 清空文本输入
textController.clear();
// 滚动到底部
_scrollToBottom();
try {
isProcessing.value = true;
isImageProcessing.value = true;
// 标记为新的AI回复
_isNewAssistantResponse = true;
// 调用Agent Service处理图片
await AgentService.processImageInput(
imagePath,
@ -433,7 +482,7 @@ class AgentController extends GetxController {
// 开始语音输入
Future<void> startVoiceInput() async {
if (isListening.value) return;
try {
await AgentService.startConversation();
} catch (e) {
@ -444,7 +493,7 @@ class AgentController extends GetxController {
// 停止语音输入
Future<void> stopVoiceInput() async {
if (!isListening.value) return;
try {
await AgentService.stopConversation();
} catch (e) {
@ -456,21 +505,20 @@ class AgentController extends GetxController {
Future<void> clearChat() async {
try {
await AgentService.clearChatHistory();
// 清空本地消息列表
messages.clear();
// 添加欢迎消息
_addWelcomeMessage();
// 重置回复状态
_isNewAssistantResponse = true;
} catch (e) {
logger.e('清除聊天历史失败: $e');
}
}
// 切换输入模式
void toggleInputMode() {
// 如果当前在图片输入模式,先清除
@ -478,9 +526,9 @@ class AgentController extends GetxController {
clearImageInput();
return;
}
isTextInputMode.toggle();
// 切换到语音模式时,直接开始语音输入
if (!isTextInputMode.value) {
startVoiceInput();
@ -498,10 +546,10 @@ class AgentController extends GetxController {
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 80, // 设置图片质量
maxWidth: 1024, // 限制最大宽度
maxHeight: 1024, // 限制最大高度
maxWidth: 1024, // 限制最大宽度
maxHeight: 1024, // 限制最大高度
);
if (image != null) {
// 设置已选择的图片
selectedImagePath.value = image.path;
@ -520,10 +568,10 @@ class AgentController extends GetxController {
final XFile? photo = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 80, // 设置图片质量
maxWidth: 1024, // 限制最大宽度
maxHeight: 1024, // 限制最大高度
maxWidth: 1024, // 限制最大宽度
maxHeight: 1024, // 限制最大高度
);
if (photo != null) {
// 设置已选择的图片
selectedImagePath.value = photo.path;
@ -534,18 +582,18 @@ class AgentController extends GetxController {
logger.e('拍照失败: $e');
}
}
// 清除图片输入状态
void clearImageInput() {
selectedImagePath.value = null;
isImageInputActive.value = false;
}
// 使用预设提示词处理图片
void useImagePrompt(String prompt) {
// 确保selectedImagePath.value不为null
if (selectedImagePath.value == null) return;
// 填充文本框同时直接发送消息
textController.text = prompt;
// 立即发送图片和提示词
@ -554,19 +602,45 @@ class AgentController extends GetxController {
// 发送后清除图片输入状态
clearImageInput();
}
// 预设提示词:总结图片内容
void summarizeImage() {
useImagePrompt('请总结这张图片的内容');
}
// 预设提示词:抽取图像文字
void extractTextFromImage() {
useImagePrompt('请提取这张图片中的所有文字');
}
// 预设提示词:翻译图像文字
void translateImageText() {
useImagePrompt('请翻译这张图片中的文字');
}
}
// 播放音乐
void musicStatus() async {
var status = await _musiceManager.getStatus();
logger.i('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying'];
currentUrl.value = status['url'];
}
// 播放音乐
void musicPlay(String url) {
_musiceManager.play(url);
isPlaying.value = true;
currentUrl.value = url;
}
// 播放音乐
void musicPause() {
_musiceManager.pause();
isPlaying.value = false;
}
void musicResume() {
_musiceManager.resume();
isPlaying.value = true;
}
}

104
lib/modules/agent/views/agent_view.dart

@ -10,7 +10,7 @@ class AgentView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Get.find<AgentController>();
return Scaffold(
appBar: AppBar(
title: Row(
@ -93,12 +93,13 @@ class AgentView extends StatelessWidget {
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
mate: message.meta,
);
},
),
),
),
// 输入区域
Container(
padding: const EdgeInsets.all(16),
@ -117,7 +118,8 @@ class AgentView extends StatelessWidget {
children: [
// 图片预览区域 - 仅在图片输入模式下显示
Obx(() {
if (controller.isImageInputActive.value && controller.selectedImagePath.value != null) {
if (controller.isImageInputActive.value &&
controller.selectedImagePath.value != null) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -154,7 +156,7 @@ class AgentView extends StatelessWidget {
),
],
),
// 预设提示选项
SizedBox(height: 8),
SingleChildScrollView(
@ -188,7 +190,7 @@ class AgentView extends StatelessWidget {
return SizedBox.shrink();
}
}),
// 输入控件行
Row(
children: [
@ -213,14 +215,14 @@ class AgentView extends StatelessWidget {
),
);
}),
const SizedBox(width: 8),
// 文本输入框或语音波形图
Expanded(
child: Obx(() {
final isTextMode = controller.isTextInputMode.value;
if (isTextMode) {
// 文本输入模式 - 注意观察图片模式时的高度变化
return Container(
@ -234,18 +236,23 @@ class AgentView extends StatelessWidget {
Expanded(
child: Obx(() {
// 图片模式下使用更高的输入框
final isImageActive = controller.isImageInputActive.value;
final isImageActive =
controller.isImageInputActive.value;
return TextField(
controller: controller.textController,
maxLines: isImageActive ? 2 : 1,
minLines: isImageActive ? 2 : 1,
decoration: InputDecoration(
hintText: isImageActive ? '添加对图片的问题或描述...' : '输入消息...',
hintText: isImageActive
? '添加对图片的问题或描述...'
: '输入消息...',
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: isImageActive ? 12 : 0),
contentPadding: EdgeInsets.symmetric(
vertical: isImageActive ? 12 : 0),
),
onSubmitted: (_) => controller.sendTextMessage(),
onSubmitted: (_) =>
controller.sendTextMessage(),
);
}),
),
@ -271,7 +278,8 @@ class AgentView extends StatelessWidget {
},
child: Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 16),
padding:
const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(24),
@ -281,7 +289,8 @@ class AgentView extends StatelessWidget {
// 语音波形图 - 始终显示
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
...List.generate(10, (index) {
return _buildSoundBar(index);
@ -289,14 +298,16 @@ class AgentView extends StatelessWidget {
const Spacer(),
// "正在聆听"文字
Obx(() {
final isListening = controller.isListening.value;
final isListening =
controller.isListening.value;
return Row(
children: [
Icon(
Icons.mic,
size: 14,
color: isListening
? Theme.of(context).primaryColor
color: isListening
? Theme.of(context)
.primaryColor
: Colors.grey,
),
const SizedBox(width: 4),
@ -304,8 +315,9 @@ class AgentView extends StatelessWidget {
'正在聆听',
style: TextStyle(
fontSize: 12,
color: isListening
? Theme.of(context).primaryColor
color: isListening
? Theme.of(context)
.primaryColor
: Colors.grey,
),
),
@ -322,9 +334,9 @@ class AgentView extends StatelessWidget {
}
}),
),
const SizedBox(width: 8),
// 图片选择按钮
Container(
width: 48,
@ -351,7 +363,8 @@ class AgentView extends StatelessWidget {
value: 'camera',
child: Row(
children: [
Icon(Icons.camera_alt, color: Theme.of(context).primaryColor),
Icon(Icons.camera_alt,
color: Theme.of(context).primaryColor),
SizedBox(width: 8),
Text('拍照'),
],
@ -361,7 +374,8 @@ class AgentView extends StatelessWidget {
value: 'gallery',
child: Row(
children: [
Icon(Icons.photo_library, color: Theme.of(context).primaryColor),
Icon(Icons.photo_library,
color: Theme.of(context).primaryColor),
SizedBox(width: 8),
Text('从相册选择'),
],
@ -379,7 +393,7 @@ class AgentView extends StatelessWidget {
),
);
}
// 构建提示选项芯片
Widget _buildPromptChip({
required BuildContext context,
@ -408,15 +422,15 @@ class AgentView extends StatelessWidget {
),
);
}
// 构建声音柱
Widget _buildSoundBar(int index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1),
child: Obx(() => _SoundBar(
delay: index * 0.1,
isActive: Get.find<AgentController>().isListening.value,
)),
delay: index * 0.1,
isActive: Get.find<AgentController>().isListening.value,
)),
);
}
}
@ -425,41 +439,42 @@ class AgentView extends StatelessWidget {
class _SoundBar extends StatefulWidget {
final double delay;
final bool isActive;
const _SoundBar({
required this.delay,
required this.isActive,
});
@override
_SoundBarState createState() => _SoundBarState();
}
class _SoundBarState extends State<_SoundBar> with SingleTickerProviderStateMixin {
class _SoundBarState extends State<_SoundBar>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_animation = Tween<double>(begin: 3, end: 15).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
)..addListener(() {
setState(() {});
});
setState(() {});
});
_startOrStopAnimation();
}
@override
void didUpdateWidget(_SoundBar oldWidget) {
super.didUpdateWidget(oldWidget);
@ -467,7 +482,7 @@ class _SoundBarState extends State<_SoundBar> with SingleTickerProviderStateMixi
_startOrStopAnimation();
}
}
void _startOrStopAnimation() {
if (widget.isActive) {
Future.delayed(Duration(milliseconds: (widget.delay * 1000).toInt()), () {
@ -481,22 +496,23 @@ class _SoundBarState extends State<_SoundBar> with SingleTickerProviderStateMixi
_controller.animateTo(0.1, duration: Duration(milliseconds: 300));
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: 2,
height: widget.isActive ? _animation.value : 3,
decoration: BoxDecoration(
color: widget.isActive ? Theme.of(context).primaryColor : Colors.grey[400],
color:
widget.isActive ? Theme.of(context).primaryColor : Colors.grey[400],
borderRadius: BorderRadius.circular(1),
),
);
}
}
}

254
lib/modules/agent/views/message_bubble.dart

@ -1,15 +1,20 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'dart:io';
import '../controllers/agent_controller.dart';
/// 消息气泡组件
class MessageBubble extends StatelessWidget {
class MessageBubble extends GetView<AgentController> {
final bool isUser;
final String message;
final DateTime timestamp;
final bool isRecognizing;
final bool hasImage;
final String? imagePath;
final Map? mate;
const MessageBubble({
Key? key,
@ -19,31 +24,42 @@ class MessageBubble extends StatelessWidget {
this.isRecognizing = false,
this.hasImage = false,
this.imagePath,
this.mate,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final timeFormat = DateFormat('HH:mm');
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisAlignment:
isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isUser) _buildAvatar(isUser, context),
const SizedBox(width: 8),
Flexible(
child: Column(
crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
crossAxisAlignment:
isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Container(
padding: hasImage ? (message == '[图片]' ? EdgeInsets.zero : const EdgeInsets.all(12)) : const EdgeInsets.all(12),
padding: hasImage
? (message == '[图片]'
? EdgeInsets.zero
: const EdgeInsets.all(12))
: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _getBubbleColor(context),
borderRadius: BorderRadius.circular(18).copyWith(
bottomLeft: isUser ? const Radius.circular(18) : const Radius.circular(4),
bottomRight: !isUser ? const Radius.circular(18) : const Radius.circular(4),
bottomLeft: isUser
? const Radius.circular(18)
: const Radius.circular(4),
bottomRight: !isUser
? const Radius.circular(18)
: const Radius.circular(4),
),
boxShadow: [
BoxShadow(
@ -79,7 +95,7 @@ class MessageBubble extends StatelessWidget {
/// 获取气泡颜色
Color _getBubbleColor(BuildContext context) {
if (isUser) {
return isRecognizing
return isRecognizing
? Color(0xFF3B82F6).withOpacity(0.8) // 识别中的用户消息使用半透明的蓝色
: Color(0xFF3B82F6); // 使用更明亮的蓝色
} else {
@ -135,7 +151,7 @@ class MessageBubble extends StatelessWidget {
],
);
}
// 如果是图片消息,显示图片
if (hasImage && imagePath != null) {
// 如果消息是默认的[图片]标记,只显示图片
@ -150,7 +166,7 @@ class MessageBubble extends StatelessWidget {
),
);
}
// 如果消息是自定义文本,显示文本+图片
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -177,18 +193,51 @@ class MessageBubble extends StatelessWidget {
],
);
}
// 判断消息内容,显示特殊卡片
if (!isUser) {
if (message.contains('播放列表') || message.contains('音乐')) {
return _buildMusicContent(context);
} else if (message.contains('景点') || message.contains('旅游')) {
return _buildTravelContent(context);
} else if (message.contains('天气') || message.contains('气温') || message.contains('预报')) {
return _buildWeatherContent(context);
if (!isUser && mate != null) {
if (mate!.keys.contains('card_music')) {
final musicName = mate!['card_music']['name'] as String;
final musicUrl = mate!['card_music']['url'] as String;
final musicSinger = mate!['card_music']['sgener'] as String;
//音乐卡片
return _buildMusicItem(context, musicName, musicUrl, musicSinger);
} else if (mate!.keys.contains('card_musiclist')) {
final rawList = mate!['card_musiclist']['musics'] as List;
final playlist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
// final playlist =
// mate!['card_musiclist']['musics'] as List<Map<String, String>>;
//音乐播放列表
return _buildMusicContent(context, playlist);
} else if (mate!.keys.contains('card_weather')) {
final city = mate!['card_weather']['city'] as String;
final rawList = mate!['card_weather']['dayweather'] as List;
final daylist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
final now = daylist[0];
//天气
return _buildWeatherContent(
context,
city,
now,
daylist,
);
}
// if (message.contains('播放列表') || message.contains('音乐')) {
// return _buildMusicContent(context);
// } else if (message.contains('景点') || message.contains('旅游')) {
// return _buildTravelContent(context);
// } else if (message.contains('天气') ||
// message.contains('气温') ||
// message.contains('预报')) {
// return _buildWeatherContent(context);
// }
}
return Text(
message,
style: TextStyle(
@ -222,11 +271,12 @@ class MessageBubble extends StatelessWidget {
}
/// 构建音乐内容卡片
Widget _buildMusicContent(BuildContext context) {
Widget _buildMusicContent(
BuildContext context, List<Map<String, dynamic>> playlist) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
const Text(
'以下是一些推荐的音乐:',
style: TextStyle(
color: Colors.black87,
@ -272,14 +322,23 @@ class MessageBubble extends StatelessWidget {
],
),
const SizedBox(height: 10),
// 歌曲示例
_buildMusicItem('雨声与钢琴曲', '纯音乐', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildMusicItem('Rainy Day', 'Coldplay', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildMusicItem('雨天的爵士咖啡馆', '爵士合集', context),
// 动态生成音乐条目
...playlist.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final name = item["name"] ?? "未知歌曲";
final url = item["url"] ?? "";
final singer = item["sgener"] ?? "未知歌手";
return Column(
children: [
_buildMusicItem(context, name, url, singer),
if (index != playlist.length - 1)
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
],
);
}).toList(),
const SizedBox(height: 8),
Center(
child: TextButton(
@ -313,7 +372,8 @@ class MessageBubble extends StatelessWidget {
label: Text('播放全部'),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).primaryColor,
side: BorderSide(color: Theme.of(context).primaryColor.withOpacity(0.5)),
side: BorderSide(
color: Theme.of(context).primaryColor.withOpacity(0.5)),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@ -344,7 +404,7 @@ class MessageBubble extends StatelessWidget {
],
);
}
/// 构建旅游内容卡片
Widget _buildTravelContent(BuildContext context) {
return Column(
@ -396,14 +456,14 @@ class MessageBubble extends StatelessWidget {
],
),
const SizedBox(height: 10),
// 景点列表
_buildLocationItem('东京塔', '标志性建筑,可俯瞰东京全景', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildLocationItem('涩谷十字路口', '世界上最繁忙的十字路口之一', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildLocationItem('浅草寺', '东京最古老的寺庙', context),
const SizedBox(height: 8),
Center(
child: TextButton(
@ -437,7 +497,8 @@ class MessageBubble extends StatelessWidget {
label: Text('查看地图'),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).primaryColor,
side: BorderSide(color: Theme.of(context).primaryColor.withOpacity(0.5)),
side: BorderSide(
color: Theme.of(context).primaryColor.withOpacity(0.5)),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@ -470,7 +531,10 @@ class MessageBubble extends StatelessWidget {
}
/// 构建音乐项目
Widget _buildMusicItem(String title, String artist, BuildContext context) {
Widget _buildMusicItem(
BuildContext context, String name, String url, String singer) {
final controller = Get.find<AgentController>(); // 获取控制器
controller.musicStatus(); //刷新状态
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
@ -494,7 +558,7 @@ class MessageBubble extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
name,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
@ -504,7 +568,7 @@ class MessageBubble extends StatelessWidget {
),
SizedBox(height: 2),
Text(
artist,
singer,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
@ -514,20 +578,43 @@ class MessageBubble extends StatelessWidget {
],
),
),
IconButton(
onPressed: () {},
icon: Icon(Icons.play_circle, color: Theme.of(context).primaryColor),
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
iconSize: 24,
),
Obx(() {
final isThisPlaying = controller.isPlaying.value &&
controller.currentUrl.value == url;
return IconButton(
onPressed: () {
if (controller.isPlaying.value) {
if (controller.currentUrl.value == url) {
controller.musicPause();
} else {
controller.musicPlay(url);
}
} else {
if (controller.currentUrl.value == url) {
controller.musicResume();
} else {
controller.musicPlay(url);
}
}
},
icon: Icon(
isThisPlaying ? Icons.pause_circle : Icons.play_circle,
color: Theme.of(context).primaryColor,
),
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
iconSize: 24,
);
}),
],
),
);
}
/// 构建地点项目
Widget _buildLocationItem(String name, String description, BuildContext context) {
Widget _buildLocationItem(
String name, String description, BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
@ -584,7 +671,8 @@ class MessageBubble extends StatelessWidget {
}
/// 构建天气内容卡片
Widget _buildWeatherContent(BuildContext context) {
Widget _buildWeatherContent(BuildContext context, String city,
Map<String, dynamic> now, List<Map<String, dynamic>> days) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -626,7 +714,7 @@ class MessageBubble extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'北京',
city,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
@ -635,7 +723,7 @@ class MessageBubble extends StatelessWidget {
),
SizedBox(height: 4),
Text(
'晴朗',
now["weatherCondition"],
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.9),
@ -645,7 +733,7 @@ class MessageBubble extends StatelessWidget {
Row(
children: [
Text(
'25°',
"${now["temperature"]}°",
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
@ -657,14 +745,14 @@ class MessageBubble extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'最高: 29°',
'最高: ${now["maxTemperature"]}°',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
Text(
'最低: 18°',
'最低: ${now["minTemperature"]}°',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
@ -698,15 +786,31 @@ class MessageBubble extends StatelessWidget {
),
),
SizedBox(height: 8),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// _buildForecastDay('周一', Icons.wb_sunny, '28°'),
// _buildForecastDay('周二', Icons.cloud, '25°'),
// _buildForecastDay('周三', Icons.wb_cloudy, '26°'),
// _buildForecastDay('周四', Icons.grain, '24°'),
// _buildForecastDay('周五', Icons.wb_sunny, '27°'),
// ],
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildForecastDay('周一', Icons.wb_sunny, '28°'),
_buildForecastDay('周二', Icons.cloud, '25°'),
_buildForecastDay('周三', Icons.wb_cloudy, '26°'),
_buildForecastDay('周四', Icons.grain, '24°'),
_buildForecastDay('周五', Icons.wb_sunny, '27°'),
],
children: List.generate(
days.length,
(index) {
String dateStr = days[index]['date'];
DateTime? date = DateUtil.getDateTime(dateStr);
String weekday = DateUtil.getWeekday(date);
return _buildForecastDay(
weekday,
Icons.wb_sunny,
days[index]['temperature'],
);
},
),
),
SizedBox(height: 16),
Container(
@ -715,7 +819,7 @@ class MessageBubble extends StatelessWidget {
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Row(
child: const Row(
children: [
Icon(
Icons.info_outline,
@ -749,7 +853,8 @@ class MessageBubble extends StatelessWidget {
label: Text('更多地区'),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).primaryColor,
side: BorderSide(color: Theme.of(context).primaryColor.withOpacity(0.5)),
side: BorderSide(
color: Theme.of(context).primaryColor.withOpacity(0.5)),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@ -780,7 +885,7 @@ class MessageBubble extends StatelessWidget {
],
);
}
/// 构建单日天气预报
Widget _buildForecastDay(String day, IconData icon, String temp) {
return Column(
@ -815,26 +920,27 @@ class MessageBubble extends StatelessWidget {
/// 动画点组件
class _AnimatedDot extends StatefulWidget {
final int delayMilliseconds;
const _AnimatedDot({required this.delayMilliseconds});
@override
_AnimatedDotState createState() => _AnimatedDotState();
}
class _AnimatedDotState extends State<_AnimatedDot> with SingleTickerProviderStateMixin {
class _AnimatedDotState extends State<_AnimatedDot>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_animation = Tween<double>(begin: 0, end: 6).animate(
CurvedAnimation(
parent: _controller,
@ -842,22 +948,22 @@ class _AnimatedDotState extends State<_AnimatedDot> with SingleTickerProviderSta
reverseCurve: Interval(0.5, 1.0, curve: Curves.easeIn),
),
)..addListener(() {
setState(() {});
});
setState(() {});
});
Future.delayed(Duration(milliseconds: widget.delayMilliseconds), () {
if (mounted) {
_controller.repeat();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
@ -876,4 +982,4 @@ class _AnimatedDotState extends State<_AnimatedDot> with SingleTickerProviderSta
),
);
}
}
}

14
lib/modules/chat/controllers/chat_controller.dart

@ -78,6 +78,9 @@ class ChatController extends GetxController {
// 取消标志
bool _shouldCancelAiResponse = false;
// 音频输入源
bool _audioSourceType = false;
// 自动停止语音识别计时器
Timer? _autoStopTimer;
@ -184,7 +187,6 @@ class ChatController extends GetxController {
// 自动停止计时器会在startVoiceRecognition中启动
}
_getLocation();
player.playerStateStream.listen((state) async {
@ -211,8 +213,6 @@ class ChatController extends GetxController {
_amapFlutter.startLocation();
}
@override
void onClose() {
Logger.info('ChatController onClose');
@ -295,7 +295,6 @@ class ChatController extends GetxController {
Future<void> sendMessage(String text) async {
if (text.trim().isEmpty) return;
// 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应");
@ -558,12 +557,11 @@ class ChatController extends GetxController {
Timer(const Duration(milliseconds: 260), () async {
if (isDebounce) return;
// 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应");
try {
final recognitionStream =
await _asrService.startContinuousRecognition();
await _asrService.startContinuousRecognition(_audioSourceType);
await Future.delayed(const Duration(milliseconds: 260));
isDebounce = true;
recognizedText.value = '';
@ -619,11 +617,11 @@ class ChatController extends GetxController {
Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return;
try {
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
final recognitionStream = await _asrService.startContinuousRecognition();
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
isVoiceRecognitionActive.value = true;
isListening.value = true;

12
lib/modules/home/views/home_view.dart

@ -157,6 +157,9 @@ class HomeView extends GetView<HomeController> {
child: PageView(
children: [
Center(
child: ClipRRect(
// 添加 ClipRRect 来实现圆角
borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小
child: SizedBox(
width: double.infinity,
height: 200.h,
@ -168,7 +171,11 @@ class HomeView extends GetView<HomeController> {
),
),
),
),
Center(
child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r),
child: Image.asset(
isDarkMode
? 'assets/images/headphone_dark.png'
@ -177,7 +184,11 @@ class HomeView extends GetView<HomeController> {
fit: BoxFit.contain,
),
),
),
Center(
child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r),
child: SizedBox(
width: double.infinity,
height: 200.h,
@ -186,6 +197,7 @@ class HomeView extends GetView<HomeController> {
? 'assets/images/glasses_light.png'
: 'assets/images/glasses_light.png',
fit: BoxFit.fitWidth,
),
),
),
),

4
lib/modules/login/controllers/login_controller.dart

@ -550,7 +550,7 @@ class LoginController extends GetxController {
requestLogin({
"mail": facebook.email,
"phone": "",
"stype": 5,
"stype": loginType,
"vcode": "",
"ttoken": facebook.token,
"phonemodel": "",
@ -579,7 +579,7 @@ class LoginController extends GetxController {
requestLogin({
"mail": google.email,
"phone": "",
"stype": 5,
"stype": loginType,
"vcode": "",
"ttoken": google.token,
"phonemodel": "",

1
lib/modules/login/views/login_view.dart

@ -575,7 +575,6 @@ class LoginView extends GetView<LoginController> {
GestureDetector(
onTap: () {
String url = 'https://www.deepsoud.com/yhxy.html';
print('----------${Get.locale?.toString()}');
if (Get.locale?.toString() != 'zh_CN') {
url = 'https://www.deepsoud.com/yhxy_en.html';
}

3
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -111,7 +111,8 @@ class MeetingRecordController extends GetxController
//开始录音
Future<void> _startRecorder() async {
final recognitionStream = await _asrService.startContinuousRecognition();
final recognitionStream =
await _asrService.startContinuousRecognition(false);
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,
);

9
lib/modules/ota_upgrade/bindings/ota_upgrade_binding.dart

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import '../controllers/ota_upgrade_controller.dart';
class OtaUpgradeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<OtaUpgradeController>(() => OtaUpgradeController());
}
}

213
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -0,0 +1,213 @@
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/network/api.dart';
import '../ota.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:json_annotation/json_annotation.dart';
class OtaUpgradeController extends GetxController {
final Dio _dio = Dio();
final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs;
String updatepackageaddress = '';
final RxDouble downloadProgress = 0.0.obs;
final RxBool isUpdating = false.obs;
final isUpdateAvailable = true.obs;
final RxDouble progress = 0.0.obs;
@override
void onInit() {
super.onInit();
_getCurrentVersion();
}
// 获取当前版本信息
Future<void> _getCurrentVersion() async {
//发送ble获取固件版本
var firmwareVersion = "1.1.0";
currentVersion.value = firmwareVersion;
//发送ble获取产品id
var id = "a2";
//发送api请求
final response = await Api.getequipmentinfo({
"id": id,
});
if (response['info'] != '') {
print('getLoginToken:进来了');
// 根据API设计调整
final resp = OtaConfigResp.fromJson(response);
print('成功解析: ${resp.id}, ${resp.factory}');
latestVersion.value = resp.systemversion!;
updatepackageaddress = resp.updatepackageaddress!;
if (_isNewVersionAvailable(resp.systemversion!)) {
//需要升级
print('需要更新');
isUpdateAvailable.value = false;
} else {
isUpdateAvailable.value = true;
//已经是最新版本更新ui,按钮隐藏
}
} else {
throw const FormatException('API 响应缺少 "info" 字段');
}
}
// 版本比较方法优化
bool _isNewVersionAvailable(String latest) {
try {
List<int> currentParts = currentVersion.value
.split('.')
.map((e) => int.parse(e.trim()))
.toList();
List<int> latestParts =
latest.split('.').map((e) => int.parse(e.trim())).toList();
for (int i = 0; i < latestParts.length; i++) {
int current = (i < currentParts.length) ? currentParts[i] : 0;
if (latestParts[i] > current) return true;
if (latestParts[i] < current) return false;
}
return false;
} catch (e) {
print('版本比较错误: $e');
return false;
}
}
// 版本更新
Future<void> checkUpdate() async {
try {
if (latestVersion.value != "")
_showUpdateDialog(latestVersion.value);
else {
Get.snackbar('错误', '获取最新版本信息失败');
}
} catch (e) {
Get.snackbar('错误', '获取版本信息失败');
}
}
// 显示更新对话框
void _showUpdateDialog(String latest) {
Get.dialog(
AlertDialog(
title: Text('发现新版本 $latest'),
content: Text('是否立即更新?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('取消'),
),
TextButton(
onPressed: () {
Get.back();
startDownload();
},
child: Text('更新'),
),
],
),
);
}
// 开始下载
Future<void> startDownload() async {
isUpdating.value = true;
// 查找最后一个 '/' 的位置
int lastIndex = updatepackageaddress.lastIndexOf('/');
// 截取最后一个 '/' 后面的字符串
String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades";
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 5),
receiveTimeout: Duration.zero,
sendTimeout: const Duration(seconds: 5),
headers: {'Accept-Encoding': 'gzip'},
));
Get.snackbar('升级命令已发送', '开始下载...');
try {
final Directory? downloadsDir = await getDownloadsDirectory();
if (downloadsDir == null) {
throw FileSystemException('无法访问下载目录');
}
// 创建子目录:Download/APP
final targetDir = Directory('${downloadsDir.path}/$directoryName');
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final savePath = '${targetDir.path}/$fileName';
print('文件保存路径: $savePath'); // 关键日志
final options = Options(
receiveTimeout: Duration.zero,
followRedirects: true,
maxRedirects: 5,
);
DateTime lastUpdate = DateTime.now();
await dio.download(
updatepackageaddress,
savePath,
options: options,
onReceiveProgress: (received, total) {
final now = DateTime.now();
if (now.difference(lastUpdate).inMilliseconds > 1000) {
lastUpdate = now;
String progressMsg = total != -1
? '${(received / total * 100).toStringAsFixed(1)}%'
: '已下载:${(received / 1024 / 1024).toStringAsFixed(1)}MB';
Get.snackbar(
'升级进度',
progressMsg,
duration: const Duration(seconds: 1),
);
}
},
);
// 下载完成后验证文件存在性
File file = File(savePath);
if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); // 明确提示路径
} else {
Get.snackbar('错误', '文件未找到,请检查写入权限');
}
isUpdating.value = false;
//下载完成,开始升级
_installApp(savePath);
} on DioException catch (e) {
Get.snackbar('下载失败', '错误: ${e.message}');
} on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}');
} catch (e) {
Get.snackbar('未知错误', e.toString());
}
}
// 开始升级
void _installApp(String path) async {
// final File file = File(path);
// if (await file.exists()) {
// // OpenResult result = await OpenFile.open(path);
// // if (result.type != ResultType.done) {
// // Get.snackbar('错误', '安装失败: ${result.message}');
// // }
// }
}
}

54
lib/modules/ota_upgrade/ota.dart

@ -0,0 +1,54 @@
class OtaConfigResp {
// 单例实例
static OtaConfigResp? _instance;
// 单例访问器(增加空安全保护)
static OtaConfigResp get instance {
if (_instance == null) {
throw Exception("User must be initialized first");
}
return _instance!;
}
// 最终属性(不可变)
final String? id;
final String? factory;
final String? equipmentname;
final String? systemversion;
final String? updatepackageaddress;
// 私有构造函数
OtaConfigResp._({
this.id,
this.factory,
this.equipmentname,
this.systemversion,
this.updatepackageaddress,
});
factory OtaConfigResp.fromJson(Map<String, dynamic> json) {
print("数据获取fronTokenJson${json}");
// 修复1:安全获取嵌套的 user 字段
final infoData = json['info'] as Map<String, dynamic>?; // 允许空值
// 更新单例实例
return _instance = OtaConfigResp._(
id: (infoData?['id'] as String?) ?? '',
factory: (infoData?['factory'] as String?) ?? '',
equipmentname: (infoData?['equipmentname'] as String?) ?? '',
systemversion: (infoData?['systemversion'] as String?) ?? '',
updatepackageaddress:
(infoData?['updatepackageaddress'] as String?) ?? '',
);
}
// 完整的JSON序列化
Map<String, dynamic> toJson() => {
'user': {
'id': instance.id,
'factory': instance.factory,
'equipmentname': instance.equipmentname,
'systemversion': instance.systemversion,
'updatepackageaddress': instance.updatepackageaddress,
}
};
}

109
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/ota_upgrade_controller.dart';
class OtaUpgradeView extends GetView<OtaUpgradeController> {
const OtaUpgradeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// 添加外层Center
child: SingleChildScrollView(
// 添加滚动防止小屏溢出
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, // 水平居中
mainAxisSize: MainAxisSize.min, // 紧凑布局
children: [
// 设备图标
CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(
Icons.phone_iphone,
size: 60,
color: Colors.blue,
),
),
SizedBox(height: 30),
// 版本信息
Column(
children: [
Text(
'当前版本',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
SizedBox(height: 8),
Text(
'v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
SizedBox(height: 40),
// 更新按钮/进度条
Obx(() {
if (controller.isUpdating.value) {
return Column(
children: [
SizedBox(
width: 200, // 限制进度条宽度
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey),
)
],
);
} else {
return ElevatedButton(
onPressed: controller.isUpdateAvailable.value
? null
: controller.checkUpdate,
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.grey
: Colors.blue,
foregroundColor: Colors.white,
padding:
EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
controller.isUpdateAvailable.value ? '已是最新版本' : '立即更新',
style: TextStyle(fontSize: 16),
),
);
}
}),
],
),
),
),
),
);
}
}

79
lib/modules/profile/controllers/profile_controller.dart

@ -12,6 +12,9 @@ import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart';
import '../../../routes/app_routes.dart';
import '../../../modules/login/controllers/googleLogin.dart';
import '../../../modules/login/controllers/facebookLogin.dart';
class ProfileController extends GetxController {
static ProfileController get to => Get.find();
@ -364,4 +367,80 @@ class ProfileController extends GetxController {
Get.snackbar('错误', '更新用户名和头像失败');
}
}
Future<void> signOut() async {
try {
//显示加载提示
// Get.dialog(
// const Center(child: CircularProgressIndicator()),
// barrierDismissible: false,
// );
// 清理所有存储
await _storage.erase();
// 或者逐个清理关键存储项
await _storage.write("logintoken", null);
await _storage.write("user_info", null);
await _storage.write("local_avatar", null);
print('清理存储成功');
// 第三方登录退出
try {
final google = Google();
final facebook = Facebook();
await Future.wait([
google.signOutGoogle(),
facebook.signOutFacebook(),
]);
} catch (e) {
print('第三方登录退出错误: $e');
// 继续执行,不中断退出流程
}
// 关闭加载提示
//Get.back();
// 确保导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
Get.snackbar('成功', '已退出登录');
print('退出登录成功');
} catch (e) {
//Get.back(); // 关闭加载提示
Get.snackbar('错误', '退出登录失败,请重试');
print('退出登录错误: $e');
}
}
// 注销账号
Future<void> deleteAccount() async {
try {
// 显示加载提示
Get.dialog(
const Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
// 删除后台用户数据
final responce = await Api.deleteUser();
if (responce == null) {
Get.back(); // 关闭加载提示
Get.snackbar('错误', '删除用户数据失败');
return;
}
// 关闭加载提示
Get.back();
signOut(); // 调用 signOut 方法
Get.snackbar('Success', 'Account has been permanently deleted');
Logger().d('账号删除成功');
} catch (e) {
Get.back(); // 关闭加载提示
Get.snackbar('错误', '删除账号失败,请重试');
Logger().d('账号删除失败: $e');
}
}
}

797
lib/modules/profile/views/profile_view.dart

@ -5,6 +5,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../core/widgets/common_bottom_nav.dart';
import '../../../routes/app_routes.dart';
import 'dart:io';
import 'dart:ui';
import '../../common/webview_page.dart';
// ProfileView: 用户设置页面
class ProfileView extends GetView<ProfileController> {
@ -143,6 +145,7 @@ class EditProfileView extends GetView<ProfileController> {
TextEditingController(text: controller.userInfo.value.name);
return Scaffold(
resizeToAvoidBottomInset: false, // 添加这一行,防止键盘挤压内容
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: true,
@ -156,107 +159,729 @@ class EditProfileView extends GetView<ProfileController> {
),
onPressed: () => Get.back(),
),
title: Text(
'editProfile'.tr,
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
// title: Text(
// 'editProfile'.tr,
// style: TextStyle(
// fontSize: 17.sp,
// fontWeight: FontWeight.w600,
// color: isDarkMode ? Colors.white : Colors.black,
// ),
// ),
actions: [
TextButton(
onPressed: () {
// 保存用户名
controller.updateUserNameAndAvatar(nameController.text);
// 返回上一级页面
Get.back();
},
child: Text(
'save'.tr,
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.blue[300] : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
],
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
_buildEditProfileSection(context, isDarkMode, nameController),
SizedBox(height: 16.h),
_buildAccountSettings(isDarkMode),
SizedBox(height: 16.h),
])));
}
// 新增:构建编辑资料区域的方法
Widget _buildEditProfileSection(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
child: Text(
'editProfile'.tr, // 编辑资料
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.blue[400] : Colors.blue[600],
letterSpacing: 1,
),
),
),
),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
// 头像编辑
Center(
child: GestureDetector(
onTap: () => controller.updateUserAvatar(),
child: Stack(
children: [
Obx(() => CircleAvatar(
radius: 50.r,
backgroundImage: controller.localAvatar.value.isNotEmpty
? FileImage(File(controller.localAvatar.value))
: (controller.userInfo.value.avatar.isNotEmpty
? NetworkImage(
controller.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
child: controller.userInfo.value.avatar.isEmpty
? Icon(Icons.person, size: 50.r)
: null,
)),
Positioned(
right: 0,
bottom: 0,
child: Container(
padding: EdgeInsets.all(4.w),
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: Icon(
Icons.camera_alt,
size: 20.w,
color: Colors.white,
)),
)
],
)),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
// 保留毛玻璃效果(如果需要)
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
// 头像设置项
_buildAvatarSetting(isDarkMode),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 昵称设置项
_buildNicknameSetting(context, isDarkMode, nameController),
],
),
),
SizedBox(height: 20.h),
),
),
],
);
}
// 昵称编辑 - 使用外部定义的controller而不是每次重建
// 新增:构建头像设置项
Widget _buildAvatarSetting(bool isDarkMode) {
return InkWell(
onTap: () => controller.updateUserAvatar(),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h), // 调整垂直间距
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // 移除,让内容自然排列
children: [
// --- 添加图标 ---
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.blue[900]!.withOpacity(0.3) // 使用示例颜色
: Colors.blue[50]!, // 使用示例颜色
borderRadius: BorderRadius.circular(16.r), // 图标背景圆角
),
child: Icon(
Icons.account_circle_outlined, // 头像图标
size: 18.w,
color:
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!, // 图标颜色
),
),
SizedBox(width: 12.w), // 图标和文字间距
// --- ---
Expanded(
// 让文字占据剩余空间
child: Text(
'avatar'.tr, // 头像
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
),
// --- 右侧内容保持不变 ---
Row(
children: [
Expanded(
child: TextField(
controller: nameController,
decoration: InputDecoration(
labelText: 'nickname'.tr,
border: const OutlineInputBorder(),
suffixIcon: nameController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
nameController.clear();
},
)
Obx(() => CircleAvatar(
radius: 30.r, // 调整头像大小以适应行高
backgroundImage: controller.localAvatar.value.isNotEmpty
? FileImage(File(controller.localAvatar.value))
: (controller.userInfo.value.avatar.isNotEmpty
? NetworkImage(controller.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
child: (controller.localAvatar.value.isEmpty &&
controller.userInfo.value.avatar.isEmpty)
? Icon(Icons.person,
size: 20.r, color: Colors.grey) // 默认图标
: null,
),
backgroundColor: Colors.grey[200], // 添加背景色防止透明图片问题
)),
SizedBox(width: 8.w),
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.chevron_right,
size: 16.w,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
),
],
),
// ... 其他编辑项 ...
// --- ---
],
),
),
);
}
SizedBox(height: 30.h),
// 添加一个大的保存按钮
SizedBox(
width: double.infinity,
height: 50.h,
child: ElevatedButton(
onPressed: () {
// 保存用户名
controller.updateUserNameAndAvatar(nameController.text);
// 返回上一级页面
Get.back();
},
style: ElevatedButton.styleFrom(
backgroundColor: isDarkMode ? Colors.blue[700] : Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.r),
),
// 新增:构建昵称设置项
Widget _buildNicknameSetting(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
return InkWell(
onTap: () => _showEditNicknameDialog(context, isDarkMode, nameController),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), // 调整垂直间距
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // 移除
children: [
// --- 添加图标 ---
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.green[900]!.withOpacity(0.3) // 使用示例颜色
: Colors.green[50]!, // 使用示例颜色
borderRadius: BorderRadius.circular(16.r), // 图标背景圆角
),
child: Icon(
Icons.edit_outlined, // 编辑图标
size: 18.w,
color: isDarkMode
? Colors.green[300]!
: Colors.green[600]!, // 图标颜色
),
),
SizedBox(width: 12.w), // 图标和文字间距
// --- ---
Expanded(
// 让文字占据剩余空间
child: Text(
'nickname'.tr, // 名字
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
child: Text(
'save'.tr,
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
// --- 右侧内容保持不变 ---
Row(
children: [
// 使用 Obx 监听 userInfo 的变化来显示昵称
Obx(() => Text(
controller.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name
: 'notSet'.tr, // 未设置
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600],
),
overflow: TextOverflow.ellipsis,
)),
SizedBox(width: 8.w),
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.chevron_right,
size: 16.w,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
),
],
),
// --- ---
],
),
),
);
}
// 新增:显示编辑昵称对话框的方法
void _showEditNicknameDialog(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
// 创建一个临时的 Controller,用于对话框内的编辑,避免直接修改主 nameController
final dialogNameController =
TextEditingController(text: nameController.text);
Get.dialog(
AlertDialog(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'editNickname'.tr, // 编辑昵称
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black87),
),
content: TextField(
controller: dialogNameController, // 使用对话框的 Controller
autofocus: true,
decoration: InputDecoration(
hintText: 'enterNickname'.tr, // 请输入昵称
hintStyle:
TextStyle(color: isDarkMode ? Colors.white54 : Colors.black38),
// 可以添加其他样式,如边框
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.white38 : Colors.grey[400]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.white38 : Colors.grey[400]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.blue[300]! : Colors.blue),
),
),
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black87),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr,
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54),
),
),
TextButton(
onPressed: () {
// 确认时,将对话框输入的值更新回主 nameController
nameController.text = dialogNameController.text;
Get.back(); // 关闭对话框
// 注意:实际的保存操作由 AppBar 中的保存按钮触发
},
child: Text(
'confirm'.tr,
style:
TextStyle(color: isDarkMode ? Colors.blue[300] : Colors.blue),
),
),
],
),
);
}
// _buildEditProfile(bool isDarkMode, TextEditingController nameController) {
// return
// //Padding(
// //padding: EdgeInsets.all(16.w),
// //child:
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Padding(
// padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
// child: Text(
// 'editProfile'.tr, // 账号设置
// style: TextStyle(
// fontSize: 12.sp,
// fontWeight: FontWeight.w500,
// color: isDarkMode ? Colors.blue[400] : Colors.blue[600],
// letterSpacing: 1,
// ),
// ),
// ),
// // 头像编辑
// Center(
// child: GestureDetector(
// onTap: () => controller.updateUserAvatar(),
// child: Stack(
// children: [
// Obx(() => CircleAvatar(
// radius: 50.r,
// backgroundImage: controller.localAvatar.value.isNotEmpty
// ? FileImage(File(controller.localAvatar.value))
// : (controller.userInfo.value.avatar.isNotEmpty
// ? NetworkImage(controller.userInfo.value.avatar)
// : null) as ImageProvider<Object>?,
// child: controller.userInfo.value.avatar.isEmpty
// ? Icon(Icons.person, size: 50.r)
// : null,
// )),
// Positioned(
// right: 0,
// bottom: 0,
// child: Container(
// padding: EdgeInsets.all(4.w),
// decoration: const BoxDecoration(
// color: Colors.blue,
// shape: BoxShape.circle,
// ),
// child: Icon(
// Icons.camera_alt,
// size: 20.w,
// color: Colors.white,
// )),
// )
// ],
// )),
// ),
// SizedBox(height: 20.h),
// // 昵称编辑 - 使用外部定义的controller而不是每次重建
// Row(
// children: [
// Expanded(
// child: TextField(
// controller: nameController,
// decoration: InputDecoration(
// labelText: 'nickname'.tr,
// border: const OutlineInputBorder(),
// suffixIcon: nameController.text.isNotEmpty
// ? IconButton(
// icon: const Icon(Icons.clear),
// onPressed: () {
// nameController.clear();
// },
// )
// : null,
// ),
// ),
// ),
// ],
// ),
// ],
// //),
// );
// }
Widget _buildAccountSettings(bool isDarkMode) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
child: Text(
'AccountSetting'.tr, // 账号设置
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.blue[400] : Colors.blue[600],
letterSpacing: 1,
),
),
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
// 用户协议
_buildSimpleNavigationSetting(
title: 'userAgreement'.tr, // 用户协议
icon: Icons.description_outlined,
iconBgColor: isDarkMode
? Colors.purple[900]!.withOpacity(0.3)
: Colors.purple[50]!,
iconColor:
isDarkMode ? Colors.purple[300]! : Colors.purple[600]!,
titleColor: isDarkMode ? Colors.white : Colors.black87,
onTap: () {
String url = 'https://www.deepsoud.com/yhxy.html';
if (Get.locale?.toString() != 'zh_CN') {
url = 'https://www.deepsoud.com/yhxy_en.html';
}
Get.to(() => WebViewPage(
url: url,
title: 'userAgreement'.tr,
));
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 隐私政策
_buildSimpleNavigationSetting(
title: 'privacyPolicy'.tr, // 隐私政策
icon: Icons.privacy_tip_outlined,
iconBgColor: isDarkMode
? Colors.purple[900]!.withOpacity(0.3)
: Colors.purple[50]!,
iconColor:
isDarkMode ? Colors.purple[300]! : Colors.purple[600]!,
titleColor: isDarkMode ? Colors.white : Colors.black87,
onTap: () {
String url = 'https://www.deepsoud.com/ysxy.html';
if (Get.locale?.toString() != 'zh_CN') {
url = 'https://www.deepsoud.com/ysxy_en.html';
}
Get.to(() => WebViewPage(
url: url,
title: 'privacyPolicy'.tr,
));
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 退出登录
_buildSimpleNavigationSetting(
title: 'logout'.tr, // 退出登录
icon: Icons.logout,
iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3)
: Colors.red[50]!,
iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor:
isDarkMode ? Colors.red[300]! : Colors.red[600]!,
onTap: () {
// 退出登录确认对话框
Get.dialog(
AlertDialog(
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'logout'.tr, // 退出登录
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'logoutTips'.tr, // 确定要退出当前账号吗?
style: TextStyle(
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
),
TextButton(
onPressed: () {
Get.back();
controller.signOut();
},
child: Text(
'confirm'.tr, // 确定
style: TextStyle(
color: isDarkMode
? Colors.red[300]
: Colors.red[600],
),
),
),
],
),
);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 注销账号
_buildSimpleNavigationSetting(
title: 'deleteAccount'.tr, // 注销账号
icon: Icons.delete_forever,
iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3)
: Colors.red[50]!,
iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor:
isDarkMode ? Colors.red[300]! : Colors.red[600]!,
onTap: () {
// 注销账号确认对话框
Get.dialog(
AlertDialog(
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'deleteAccount'.tr, // 注销账号
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'deleteAccountTips'.tr, // 确定要永久删除您的账号吗?此操作不可撤销
style: TextStyle(
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
),
TextButton(
onPressed: () {
Get.back();
controller.deleteAccount();
},
child: Text(
'confirm'.tr, // 确定
style: TextStyle(
color: isDarkMode
? Colors.red[300]
: Colors.red[600],
),
),
),
],
),
);
},
isDarkMode: isDarkMode,
),
],
),
),
),
),
],
);
}
// 简单导航设置项
Widget _buildSimpleNavigationSetting({
required String title,
String? subtitle,
required IconData icon,
required Color iconBgColor,
required Color iconColor,
Color? titleColor,
Color? subtitleColor,
required VoidCallback onTap,
required bool isDarkMode,
}) {
return InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(16.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
icon,
size: 16.w,
color: iconColor,
),
),
SizedBox(width: 12.w),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: titleColor ??
(isDarkMode ? Colors.white : Colors.black87),
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null) SizedBox(height: 2.h),
if (subtitle != null)
Text(
subtitle,
style: TextStyle(
fontSize: 12.sp,
color: subtitleColor ??
(isDarkMode
? Colors.white70
: Colors.black54),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.chevron_right,
size: 16.w,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
),
],

92
lib/modules/settings/controllers/settings_controller.dart

@ -8,13 +8,10 @@ import '../../../data/services/speech_factory.dart';
import '../models/language_model.dart';
import 'package:logger/logger.dart';
import '../../../data/models/user_Info.dart';
import '../../../modules/login/controllers/googleLogin.dart';
import '../../../modules/login/controllers/facebookLogin.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import '../../../routes/app_routes.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../../data/services/network/api.dart';
class SettingsController extends GetxController {
// 语言设置
@ -40,6 +37,17 @@ class SettingsController extends GetxController {
name: 'vietnamese', alias: 'Tiếng Việt', value: 'vi_VN'), // 添加越南语选项
LanguageModel(
name: 'italian', alias: 'Italiano', value: 'it_IT'), // 添加意大利语选项
LanguageModel(
name: 'hindi', alias: 'हिन्दी', value: 'hi_IN'), // 添加印度语(印地语)选项
LanguageModel(
name: 'indonesian',
alias: 'Bahasa Indonesia',
value: 'id_ID'), // 添加印尼语选项
LanguageModel(name: 'thai', alias: 'ไทย', value: 'th_TH'), // 添加泰语选项
LanguageModel(
name: 'malay', alias: 'Bahasa Melayu', value: 'ms_MY'), // 添加马来语选项
LanguageModel(name: 'turkish', alias: 'Türkçe', value: 'tr_TR'), // 添加土耳其语选项
LanguageModel(name: 'polish', alias: 'Polski', value: 'pl_PL'), // 添加波兰语选项
];
Rx<LanguageModel> language = LanguageModel().obs;
@ -316,80 +324,4 @@ class SettingsController extends GetxController {
sourceLanguage.value = targetLanguage.value;
targetLanguage.value = temp;
}
Future<void> signOut() async {
try {
//显示加载提示
// Get.dialog(
// const Center(child: CircularProgressIndicator()),
// barrierDismissible: false,
// );
// 清理所有存储
await _storage.erase();
// 或者逐个清理关键存储项
await _storage.write("logintoken", null);
await _storage.write("user_info", null);
await _storage.write("local_avatar", null);
print('清理存储成功');
// 第三方登录退出
try {
final google = Google();
final facebook = Facebook();
await Future.wait([
google.signOutGoogle(),
facebook.signOutFacebook(),
]);
} catch (e) {
print('第三方登录退出错误: $e');
// 继续执行,不中断退出流程
}
// 关闭加载提示
//Get.back();
// 确保导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
Get.snackbar('成功', '已退出登录');
print('退出登录成功');
} catch (e) {
//Get.back(); // 关闭加载提示
Get.snackbar('错误', '退出登录失败,请重试');
print('退出登录错误: $e');
}
}
// 注销账号
Future<void> deleteAccount() async {
try {
// 显示加载提示
Get.dialog(
const Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
// 删除后台用户数据
final responce = await Api.deleteUser();
if (responce == null) {
Get.back(); // 关闭加载提示
Get.snackbar('错误', '删除用户数据失败');
return;
}
// 关闭加载提示
Get.back();
signOut(); // 调用 signOut 方法
Get.snackbar('Success', 'Account has been permanently deleted');
Logger().d('账号删除成功');
} catch (e) {
Get.back(); // 关闭加载提示
Get.snackbar('错误', '删除账号失败,请重试');
Logger().d('账号删除失败: $e');
}
}
}

307
lib/modules/settings/views/settings_view.dart

@ -59,7 +59,9 @@ class SettingsView extends GetView<SettingsController> {
// 用户信息卡片
Widget _buildUserInfoCard(bool isDarkMode) {
return Container(
return GestureDetector(
onTap: () => Get.toNamed(Routes.editProfile),
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
gradient: LinearGradient(
@ -113,10 +115,6 @@ class SettingsView extends GetView<SettingsController> {
borderRadius: BorderRadius.circular(30.r),
child: controller.userInfo.value.avatar.isEmpty
?
// Image.network(
// 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80',
// fit: BoxFit.cover, // 图片填充方式
// )
Container(
decoration: BoxDecoration(
color: Colors.blue[600],
@ -124,7 +122,8 @@ class SettingsView extends GetView<SettingsController> {
),
child: Center(
child: Text(
controller.userInfo.value.name.isNotEmpty
controller
.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name[0]
.toUpperCase()
: '?',
@ -200,7 +199,8 @@ class SettingsView extends GetView<SettingsController> {
1: 'standardUser', //'标准用户'
2: 'proUser', //'专业用户'
};
final membershipString = membershipMap[controller
final membershipString = membershipMap[
controller
.userInfo.value.membershipLevel] ??
'Unknown';
return Text(
@ -212,24 +212,24 @@ class SettingsView extends GetView<SettingsController> {
);
}),
),
SizedBox(width: 8.w),
Flexible(
child: Text(
'${'joinDevice'.tr} DeepSound Pro', // 已连接设备:
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode
? Colors.white70
: Colors.black54,
),
overflow: TextOverflow.ellipsis,
),
),
// SizedBox(width: 8.w),
// Flexible(
// child: Text(
// '${'joinDevice'.tr} DeepSound Pro', // 已连接设备:
// style: TextStyle(
// fontSize: 12.sp,
// color: isDarkMode
// ? Colors.white70
// : Colors.black54,
// ),
// overflow: TextOverflow.ellipsis,
// ),
// ),
],
),
SizedBox(height: 4.h),
Text(
controller.userInfo.value.uid,
'ID:${controller.userInfo.value.uid}',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
@ -265,6 +265,7 @@ class SettingsView extends GetView<SettingsController> {
),
)),
],
),
),
),
),
@ -554,8 +555,11 @@ class SettingsView extends GetView<SettingsController> {
title: '语音测试',
subtitle: '测试语音识别和合成功能,支持多种服务实现',
icon: Icons.record_voice_over,
iconBgColor: isDarkMode ? Colors.orange[900]!.withOpacity(0.3) : Colors.orange[100]!,
iconColor: isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
iconBgColor: isDarkMode
? Colors.orange[900]!.withOpacity(0.3)
: Colors.orange[100]!,
iconColor:
isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
onTap: () => Get.toNamed(Routes.speechTest),
isDarkMode: isDarkMode,
),
@ -564,66 +568,17 @@ class SettingsView extends GetView<SettingsController> {
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 退出登录
// 测试ble耳机固件版本升级
_buildSimpleNavigationSetting(
title: 'logout'.tr, // 退出登录
icon: Icons.logout,
title: 'ota升级',
subtitle: '测试ble耳机固件版本升级',
icon: Icons.record_voice_over,
iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3)
: Colors.red[50]!,
iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor:
isDarkMode ? Colors.red[300]! : Colors.red[600]!,
onTap: () {
// 退出登录确认对话框
Get.dialog(
AlertDialog(
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'logout'.tr, // 退出登录
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'logoutTips'.tr, // 确定要退出当前账号吗?
style: TextStyle(
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
),
TextButton(
onPressed: () {
Get.back();
controller.signOut();
},
child: Text(
'confirm'.tr, // 确定
style: TextStyle(
color: isDarkMode
? Colors.red[300]
: Colors.red[600],
),
),
),
],
),
);
},
? Colors.orange[900]!.withOpacity(0.3)
: Colors.orange[100]!,
iconColor:
isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
onTap: () => Get.toNamed(Routes.OTA_UPGRADE),
isDarkMode: isDarkMode,
),
Divider(
@ -632,67 +587,134 @@ class SettingsView extends GetView<SettingsController> {
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 退出登录
// _buildSimpleNavigationSetting(
// title: 'logout'.tr, // 退出登录
// icon: Icons.logout,
// iconBgColor: isDarkMode
// ? Colors.red[900]!.withOpacity(0.3)
// : Colors.red[50]!,
// iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
// titleColor:
// isDarkMode ? Colors.red[300]! : Colors.red[600]!,
// onTap: () {
// // 退出登录确认对话框
// Get.dialog(
// AlertDialog(
// backgroundColor:
// isDarkMode ? Colors.grey[900] : Colors.white,
// title: Text(
// 'logout'.tr, // 退出登录
// style: TextStyle(
// color: isDarkMode ? Colors.white : Colors.black87,
// ),
// ),
// content: Text(
// 'logoutTips'.tr, // 确定要退出当前账号吗?
// style: TextStyle(
// color:
// isDarkMode ? Colors.white70 : Colors.black54,
// ),
// ),
// actions: [
// TextButton(
// onPressed: () => Get.back(),
// child: Text(
// 'cancel'.tr, // 取消
// style: TextStyle(
// color: isDarkMode
// ? Colors.white70
// : Colors.black54,
// ),
// ),
// ),
// TextButton(
// onPressed: () {
// Get.back();
// controller.signOut();
// },
// child: Text(
// 'confirm'.tr, // 确定
// style: TextStyle(
// color: isDarkMode
// ? Colors.red[300]
// : Colors.red[600],
// ),
// ),
// ),
// ],
// ),
// );
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// 注销账号
_buildSimpleNavigationSetting(
title: 'deleteAccount'.tr, // 注销账号
icon: Icons.delete_forever,
iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3)
: Colors.red[50]!,
iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor:
isDarkMode ? Colors.red[300]! : Colors.red[600]!,
onTap: () {
// 注销账号确认对话框
Get.dialog(
AlertDialog(
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'deleteAccount'.tr, // 注销账号
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'deleteAccountTips'.tr, // 确定要永久删除您的账号吗?此操作不可撤销
style: TextStyle(
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
),
TextButton(
onPressed: () {
Get.back();
controller.deleteAccount();
},
child: Text(
'confirm'.tr, // 确定
style: TextStyle(
color: isDarkMode
? Colors.red[300]
: Colors.red[600],
),
),
),
],
),
);
},
isDarkMode: isDarkMode,
),
// _buildSimpleNavigationSetting(
// title: 'deleteAccount'.tr, // 注销账号
// icon: Icons.delete_forever,
// iconBgColor: isDarkMode
// ? Colors.red[900]!.withOpacity(0.3)
// : Colors.red[50]!,
// iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
// titleColor:
// isDarkMode ? Colors.red[300]! : Colors.red[600]!,
// onTap: () {
// // 注销账号确认对话框
// Get.dialog(
// AlertDialog(
// backgroundColor:
// isDarkMode ? Colors.grey[900] : Colors.white,
// title: Text(
// 'deleteAccount'.tr, // 注销账号
// style: TextStyle(
// color: isDarkMode ? Colors.white : Colors.black87,
// ),
// ),
// content: Text(
// 'deleteAccountTips'.tr, // 确定要永久删除您的账号吗?此操作不可撤销
// style: TextStyle(
// color:
// isDarkMode ? Colors.white70 : Colors.black54,
// ),
// ),
// actions: [
// TextButton(
// onPressed: () => Get.back(),
// child: Text(
// 'cancel'.tr, // 取消
// style: TextStyle(
// color: isDarkMode
// ? Colors.white70
// : Colors.black54,
// ),
// ),
// ),
// TextButton(
// onPressed: () {
// Get.back();
// controller.deleteAccount();
// },
// child: Text(
// 'confirm'.tr, // 确定
// style: TextStyle(
// color: isDarkMode
// ? Colors.red[300]
// : Colors.red[600],
// ),
// ),
// ),
// ],
// ),
// );
// },
// isDarkMode: isDarkMode,
// ),
],
),
),
@ -1190,8 +1212,7 @@ class SettingsView extends GetView<SettingsController> {
Obx(() {
final serviceType = controller.speechServiceType.value;
final serviceName = controller.availableSpeechServices
.firstWhere(
(service) => service['type'] == serviceType,
.firstWhere((service) => service['type'] == serviceType,
orElse: () => {'name': 'unknown'})['name'];
return Container(
constraints: BoxConstraints(maxWidth: 120.w), // 限制最大宽度

150
lib/modules/speech_test/controllers/speech_test_controller.dart

@ -11,47 +11,48 @@ import '../../../core/utils/logger.dart';
class SpeechTestController extends GetxController {
// 语音服务工厂
final SpeechFactory _speechFactory = Get.find<SpeechFactory>();
// 当前使用的TTS服务
late TtsService _ttsService;
// 当前使用的ASR服务
late AsrService _asrService;
// 状态变量
final RxBool isInitialized = false.obs;
final RxBool isProcessing = false.obs;
final RxBool isListening = false.obs;
final RxBool isSpeaking = false.obs;
// 识别结果
final RxString intermediateResult = ''.obs; // 中间结果
final RxString finalResult = ''.obs; // 最终结果
final RxString finalResult = ''.obs; // 最终结果
final RxString recognitionStatus = '准备就绪'.obs;
final RxString detectedLanguage = ''.obs; // 检测到的语言
final RxString detectedLanguage = ''.obs; // 检测到的语言
// TTS事件
final RxString ttsStatus = '准备就绪'.obs;
// 当前选择的语音服务类型
final Rx<SpeechServiceType> selectedServiceType = SpeechServiceType.azure.obs;
// 音频输入源
bool _audioSourceType = false;
// 连续识别结果流
StreamSubscription<RecognitionEvent>? _recognitionSubscription;
// TTS事件流
StreamSubscription<TtsEvent>? _ttsEventSubscription;
// 文本控制器
final TextEditingController textController = TextEditingController();
// 连续播放的测试句子
final List<String> testSentences = [
'这是第一句测试语音。',
'这是第二句测试语音,语音合成支持连续播放。',
'这是第三句测试语音,将在连续模式下播放完毕。'
];
@override
void onInit() {
super.onInit();
@ -59,23 +60,23 @@ class SpeechTestController extends GetxController {
textController.text = '欢迎使用语音测试,这是一段默认的测试文本。您可以修改此文本,然后点击播放按钮测试语音合成效果。';
_initServices();
}
/// 初始化服务
Future<void> _initServices() async {
try {
isProcessing.value = true;
// 获取服务实例
_ttsService = _speechFactory.getTtsService();
_asrService = _speechFactory.getAsrService();
// 初始化服务
await _ttsService.initialize();
await _asrService.initialize();
// 监听TTS事件
_setupTtsEventListener();
isInitialized.value = true;
ttsStatus.value = '准备就绪';
recognitionStatus.value = '准备就绪';
@ -87,12 +88,12 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 设置TTS事件监听
void _setupTtsEventListener() {
// 取消旧的订阅
_ttsEventSubscription?.cancel();
// 设置新的订阅
_ttsEventSubscription = _ttsService.onEvent.listen((event) {
switch (event.type) {
@ -115,18 +116,18 @@ class SpeechTestController extends GetxController {
}
});
}
/// 设置ASR事件监听
void _setupAsrEventListener(Stream<RecognitionEvent> stream) {
// 取消旧的订阅
_recognitionSubscription?.cancel();
// 重置识别结果
intermediateResult.value = '';
finalResult.value = '';
detectedLanguage.value = '';
recognitionStatus.value = '正在等待语音输入...';
// 设置新的订阅
_recognitionSubscription = stream.listen((event) {
switch (event.type) {
@ -134,14 +135,14 @@ class SpeechTestController extends GetxController {
isListening.value = true;
recognitionStatus.value = '开始聆听...';
break;
case RecognitionEventType.sessionStopped:
isListening.value = false;
recognitionStatus.value = '聆听已结束';
// 清空中间结果,保留最终结果
intermediateResult.value = '';
break;
case RecognitionEventType.intermediateResult:
// 更新中间结果,显示为斜体或不同颜色
intermediateResult.value = event.text;
@ -150,7 +151,7 @@ class SpeechTestController extends GetxController {
}
recognitionStatus.value = '正在识别...';
break;
case RecognitionEventType.finalResult:
// 更新最终结果,显示为正常文本
finalResult.value = event.text;
@ -160,12 +161,12 @@ class SpeechTestController extends GetxController {
}
recognitionStatus.value = '识别完成';
break;
case RecognitionEventType.error:
isListening.value = false;
recognitionStatus.value = '识别错误: ${event.error}';
break;
case RecognitionEventType.canceled:
isListening.value = false;
recognitionStatus.value = '识别已取消';
@ -179,21 +180,21 @@ class SpeechTestController extends GetxController {
intermediateResult.value = '';
});
}
/// 切换语音服务类型
void switchServiceType(SpeechServiceType type) async {
if (selectedServiceType.value == type) return;
try {
isProcessing.value = true;
// 停止当前的所有服务状态
await stopAll();
// 切换服务类型
_speechFactory.setServiceType(type);
selectedServiceType.value = type;
// 重新初始化服务
await _initServices();
} catch (e) {
@ -202,40 +203,40 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 停止所有活动
Future<void> stopAll() async {
// 停止ASR
if (isListening.value) {
await stopContinuousRecognition();
}
// 停止TTS
if (isSpeaking.value) {
await stopSpeaking();
}
// 取消所有订阅
await _recognitionSubscription?.cancel();
_recognitionSubscription = null;
}
/// 单次播放文本
Future<void> speakOnce() async {
if (!isInitialized.value || isProcessing.value) return;
final text = textController.text.trim();
if (text.isEmpty) {
ttsStatus.value = '请输入要播放的文本';
return;
}
try {
isProcessing.value = true;
ttsStatus.value = '准备播放...';
final result = await _ttsService.speakOnce(text);
if (!result) {
ttsStatus.value = '播放失败';
}
@ -246,15 +247,15 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 连续播放测试
Future<void> speakContinuously() async {
if (!isInitialized.value || isProcessing.value) return;
try {
isProcessing.value = true;
ttsStatus.value = '准备连续播放...';
// 为避免阻塞,使用一个句子接一个句子的方式添加到队列
for (final sentence in testSentences) {
if (!await _ttsService.speakStream(sentence)) {
@ -264,10 +265,9 @@ class SpeechTestController extends GetxController {
// 给前一个句子一点处理时间
await Future.delayed(const Duration(milliseconds: 50));
}
// 刷新队列,确保播放完成
await _ttsService.flushStream();
} catch (e) {
ttsStatus.value = '连续播放异常: $e';
Logger.error('连续播放异常: $e');
@ -275,11 +275,11 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 停止播放
Future<void> stopSpeaking() async {
if (!isInitialized.value) return;
try {
await _ttsService.stop();
} catch (e) {
@ -287,39 +287,39 @@ class SpeechTestController extends GetxController {
Logger.error('停止播放异常: $e');
}
}
/// 一次性识别
Future<void> recognizeOnce() async {
if (!isInitialized.value || isProcessing.value || isListening.value) return;
try {
isProcessing.value = true;
recognitionStatus.value = '开始识别...';
// 清空之前的结果
intermediateResult.value = '';
finalResult.value = '';
detectedLanguage.value = '';
// 显示正在进行的状态
recognitionStatus.value = '正在聆听...';
// 执行一次性识别 - 这是同步等待结果的操作
final result = await _asrService.recognizeOnce();
if (result.isError) {
recognitionStatus.value = '识别错误: ${result.error}';
Logger.error('一次性识别错误: ${result.error}');
} else {
// 更新最终结果
finalResult.value = result.text;
// 更新语言信息
if (result.detectedLanguage.isNotEmpty) {
detectedLanguage.value = result.detectedLanguage;
Logger.debug('检测到语言: ${result.detectedLanguage}');
}
recognitionStatus.value = '识别完成';
}
} catch (e) {
@ -329,26 +329,26 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 开始连续识别
Future<void> startContinuousRecognition() async {
if (!isInitialized.value || isProcessing.value || isListening.value) return;
try {
isProcessing.value = true;
// 清空之前的结果
intermediateResult.value = '';
finalResult.value = '';
detectedLanguage.value = '';
recognitionStatus.value = '准备连续识别...';
// 获取识别事件流 - 这启动了异步识别过程
final stream = await _asrService.startContinuousRecognition();
final stream =
await _asrService.startContinuousRecognition(_audioSourceType);
// 设置事件监听 - 异步处理识别结果
_setupAsrEventListener(stream);
} catch (e) {
recognitionStatus.value = '启动连续识别异常: $e';
Logger.error('启动连续识别异常: $e');
@ -359,19 +359,19 @@ class SpeechTestController extends GetxController {
isProcessing.value = false;
}
}
/// 停止连续识别
Future<void> stopContinuousRecognition() async {
if (!isInitialized.value || !isListening.value) return;
try {
// 首先更新状态,防止重复调用
isListening.value = false;
recognitionStatus.value = '正在停止识别...';
// 停止识别过程
await _asrService.stopContinuousRecognition();
// 更新UI状态
recognitionStatus.value = '连续识别已停止';
intermediateResult.value = '';
@ -380,16 +380,16 @@ class SpeechTestController extends GetxController {
Logger.error('停止连续识别异常: $e');
}
}
// 获取识别结果(用于UI显示)
String getDisplayResult() {
// 如果有最终结果,优先显示
String displayText = '';
if (finalResult.value.isNotEmpty) {
displayText = finalResult.value;
}
// 如果有中间结果,添加到最终结果后(可在UI中使用不同样式显示)
if (intermediateResult.value.isNotEmpty) {
if (displayText.isNotEmpty) {
@ -398,19 +398,19 @@ class SpeechTestController extends GetxController {
displayText = '正在识别: ${intermediateResult.value}';
}
}
return displayText;
}
@override
void onClose() {
// 取消所有订阅
_recognitionSubscription?.cancel();
_ttsEventSubscription?.cancel();
// 释放控制器
textController.dispose();
super.onClose();
}
}
}

21
lib/modules/splash/controllers/splash_controller.dart

@ -21,7 +21,7 @@ class SplashController extends GetxController {
bool get isResourceLoaded => _isResourceLoaded.value;
// 版本号
final String version = 'v1.0.0';
//final String version = 'V1.0.0';
// 主题服务
final ThemeService _themeService = ThemeService();
@ -32,20 +32,29 @@ class SplashController extends GetxController {
WidgetsBinding.instance.addPostFrameCallback((_) {
_startLoadingAnimation();
_loadAppResources();
_initMapSDK();
});
}
// 初始化地图SDK
void _initMapSDK() {
try {
AmapFlutter().initSdk(
iosKey: "a4a1394fe817c2f86a424b897b4a9af4",
androidKey: "e5fdc9605eabdeb5626f18f5721f343d",
);
});
} catch (e) {
print('地图SDK初始化失败: $e');
}
}
// 进度条动画(持续2秒)
void _startLoadingAnimation() {
const totalDuration = 500; // 动画总时长2秒
const steps = totalDuration ~/ 50; // 40次更新(50ms间隔)
final stepValue = 1.0 / steps;
const totalDuration = 1500; // 动画总时长增加到1.5秒
const steps = totalDuration ~/ 30; // 30的间隔,更平滑的动画
const stepValue = 1.0 / steps;
Timer.periodic(const Duration(milliseconds: 50), (timer) {
Timer.periodic(const Duration(milliseconds: 30), (timer) {
if (_loadingProgress.value < 1.0) {
_loadingProgress.value += stepValue;
} else {

69
lib/modules/splash/views/splash_view.dart

@ -40,7 +40,7 @@ class SplashView extends GetView<SplashController> {
),
// 版本号
_buildVersionInfo(isDarkMode),
//_buildVersionInfo(isDarkMode),
],
),
);
@ -148,6 +148,7 @@ class SplashView extends GetView<SplashController> {
padding: EdgeInsets.symmetric(horizontal: 80.w),
child: Stack(
children: [
// 背景条
Container(
height: 4.h,
decoration: BoxDecoration(
@ -155,23 +156,28 @@ class SplashView extends GetView<SplashController> {
borderRadius: BorderRadius.circular(2.r),
),
),
// 进度条
Obx(() {
final progress = controller.loadingProgress;
return LayoutBuilder(
builder: (context, constraints) {
return Container(
height: 4.h,
width: constraints.maxWidth * progress,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.blue, Colors.purple],
),
borderRadius: BorderRadius.circular(2.r),
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 4.h,
width: 240.w * progress, // 使用固定宽度乘以进度
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.blue, Colors.purple],
),
borderRadius: BorderRadius.circular(2.r),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 5,
offset: const Offset(0, 1),
),
);
},
],
),
);
}),
],
@ -201,20 +207,21 @@ class SplashView extends GetView<SplashController> {
}
// 版本信息
Widget _buildVersionInfo(bool isDarkMode) {
return Positioned(
bottom: 32.h,
left: 0,
right: 0,
child: Center(
child: Text(
controller.version,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
),
),
),
);
}
// 暂时不需要版本号显示,保留代码以备将来使用
// Widget _buildVersionInfo(bool isDarkMode) {
// return Positioned(
// bottom: 32.h,
// left: 0,
// right: 0,
// child: Center(
// child: Text(
// controller.version,
// style: TextStyle(
// fontSize: 12.sp,
// color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
// ),
// ),
// ),
// );
// }
}

44
lib/modules/translation/controllers/translation_controller.dart

@ -9,28 +9,9 @@ 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 'package:jieli/jieli.dart';
import '../../../data/services/ble_manager.dart';
class TranslationController extends GetxController {
late Jieli jieli;
// 事件订阅
StreamSubscription? _scanSubscription;
StreamSubscription? _connectionSubscription;
StreamSubscription? _wakeupSubscription;
StreamSubscription? _audioSubscription;
StreamSubscription? _commandResultSubscription;
// 状态变量
var isScanning = false.obs;
var deviceInfo = '未获取'.obs;
var connectStatus = '未连接'.obs;
var currentDevice = Rx<BleDevice?>(null);
var scannedDevices = RxList<BleDevice>([]);
var wakeupReceived = false.obs;
var audioDataSize = 0.obs;
var receivedFrames = 0.obs;
var audioDataReceiving = false.obs;
var volumeCommandResult = ''.obs;
// 服务
final AsrService _asrService = Get.find<AsrService>();
final VolcanoTranslationService _translationService =
@ -38,7 +19,10 @@ class TranslationController extends GetxController {
final TtsService _ttsService = Get.find<TtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
// 蓝牙服务
final _bleManager = Get.find<BleManager>();
// 音频输入源
bool _audioSourceType = false;
// 存储相关
static const String _historyKey = 'translation_history';
static const String _sourceLanguageKey = 'translation_source_language';
@ -104,7 +88,7 @@ class TranslationController extends GetxController {
@override
void onInit() {
super.onInit();
jieli = Jieli();
//_setupEventListeners();
// 加载上次保存的语言设置
_loadSavedLanguages();
@ -240,19 +224,23 @@ class TranslationController extends GetxController {
// // 添加到历史记录
// translationHistory.add(newItem);
// 开始连续语音识别
if (currentMode.value == "audioVideo") {
_bleManager.openDecoder();
// 发送ble音乐或者通话远端声音
_audioSourceType = true;
Logger.info('发送ble音乐或者通话远端声音');
} else if (currentMode.value == "call") {
// 发送ble系统mic和dac(音乐或者通话远端)声音
_bleManager.openA2DPDecoder();
_audioSourceType = true;
Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音');
} else {
// 开始连续语音识别
_audioSourceType = false;
}
// 开始连续语音识别
final recognitionStream = await _asrService.startContinuousRecognition();
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
isRecognizing.value = true;
// 监听识别结果

56
lib/routes/app_pages.dart

@ -41,6 +41,8 @@ import '../modules/speech_test/views/speech_test_view.dart';
import '../modules/speech_test/bindings/speech_test_binding.dart';
import '../modules/agent/views/agent_view.dart';
import '../modules/agent/bindings/agent_binding.dart';
import '../modules/ota_upgrade/views/ota_upgrade_view.dart';
import '../modules/ota_upgrade/bindings/ota_upgrade_binding.dart';
abstract class AppPages {
static final pages = [
@ -48,113 +50,163 @@ abstract class AppPages {
name: Routes.splash,
page: () => const SplashView(),
binding: SplashBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.login,
page: () => const LoginView(),
binding: LoginBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.pairing,
page: () => const PairingView(),
binding: PairingBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.initial,
page: () => const HomeView(),
binding: HomeBinding(),
transition: Transition.noTransition,
),
GetPage(
name: Routes.home,
page: () => const HomeView(),
binding: HomeBinding(),
transition: Transition.noTransition,
),
GetPage(
name: Routes.explore,
page: () => const ExploreView(),
binding: ExploreBinding(),
transition: Transition.noTransition,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.chat,
page: () => const ChatView(),
binding: ChatBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.recording,
page: () => const ChatView(),
binding: ChatBinding(),
),
GetPage(
name: Routes.OTA_UPGRADE,
page: () => const OtaUpgradeView(),
binding: OtaUpgradeBinding(),
),
GetPage(
name: Routes.profile,
page: () => const ProfileView(),
binding: ProfileBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.editProfile,
page: () => const EditProfileView(),
binding: ProfileBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.translation,
page: () => const TranslationView(),
binding: TranslationBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.translationHistory,
page: () => const TranslationHistoryView(),
binding: TranslationHistoryBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.meeting,
page: () => const MeetingView(),
binding: MeetingBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.meetingRecord,
page: () => const MeetingRecordView(),
binding: MeetingRecordBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.meetingDetails,
page: () => const MeetingDetailsView(),
binding: MeetingDetailsBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.meetingEdit,
page: () => const MeetingEditView(),
binding: MeetingEditBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.timbre,
page: () => const TimbreView(),
binding: TimbreBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.settings,
page: () => const SettingsView(),
binding: SettingsBinding(),
transition: Transition.noTransition,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.feedback,
page: () => const FeedbackView(),
binding: FeedbackBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.jieliTest,
page: () => const JieliTestView(),
binding: JieliTestBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.opusTest,
page: () => const OpusTestView(),
binding: OpusTestBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.speechTest,
page: () => const SpeechTestView(),
binding: SpeechTestBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage(
name: Routes.agent,
page: () => const AgentView(),
binding: AgentBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
];
}

2
lib/routes/app_routes.dart

@ -6,6 +6,8 @@ abstract class Routes {
static const home = '/home';
static const explore = '/explore';
static const chat = '/chat';
static const OTA_UPGRADE = '/ota_upgrade';
static const recording = '/recording';
static const profile = '/profile';
static const meeting = '/meeting';
static const meetingRecord = '/meetingRecord';

1
local_plugins/agent_service/android/build.gradle.kts

@ -48,4 +48,5 @@ dependencies {
add("compileOnly", project(":deepvoice_utils"))
add("compileOnly", project(":chat_storage"))
add("compileOnly", project(":ble_service"))
add("compileOnly", project(":music_service"))
}

4
local_plugins/agent_service/android/settings.gradle.kts

@ -5,11 +5,11 @@ include(":azure_speech")
include(":open_ai_service")
include(":deepvoice_utils")
include(":chat_storage")
include(":deepvoice_utils")
include(":music_service")
// 设置项目路径
project(":azure_speech").projectDir = file("../../azure_speech/android")
project(":open_ai_service").projectDir = file("../../open_ai_service/android")
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android")
project(":chat_storage").projectDir = file("../../chat_storage/android")
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android")
project(":music_service").projectDir = file("../../music_service/android")

89
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -7,6 +7,7 @@ import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.azure_speech.AzureTtsHelper
import com.yunqiinnovation.open_ai_service.OpenAIService
import com.yunqiinnovation.chat_storage.ChatStorageHelper
import com.yunqiinnovation.music_service.MusicServiceStarter
import com.yunqiinnovation.deepvoice_utils.FileLogger
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.*
@ -138,6 +139,7 @@ object AgentService : CoroutineScope {
// 自定义系统提示词
val customSystemPrompt = config["systemPrompt"] as? String
FileLogger.i(TAG, "初始化 系统提示词:$config")
if (!customSystemPrompt.isNullOrBlank()) {
systemPrompt = customSystemPrompt
} else {
@ -295,11 +297,11 @@ object AgentService : CoroutineScope {
// 检查录音权限
// if (!isExternalActive||!checkRecordAudioPermission()) {
// FileLogger.e(TAG, "需要录音权限,请在设置中授予权限")
// sendEvent("error", mapOf("code" to "PERMISSION_DENIED", "message" to "需要录音权限"))
// return false
// }
if (!isExternalActive&&!checkRecordAudioPermission()) {
FileLogger.e(TAG, "需要录音权限,请在设置中授予权限")
sendEvent("error", mapOf("code" to "PERMISSION_DENIED", "message" to "需要录音权限"))
return false
}
isRecognitionActive = true
hasSpeechDetected = false
@ -568,6 +570,7 @@ object AgentService : CoroutineScope {
// 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder()
var metadata:String = ""
// 添加系统提示词到历史消息的副本中
val messagesWithSystemPrompt = JSONArray()
@ -646,7 +649,7 @@ object AgentService : CoroutineScope {
addToHistoryMessages(openAIService.createAssistantMessage(response))
// 保存聊天记录
saveChatMessage(displayText, response)
saveChatMessage(displayText, response,metadata)
}
// 标记AI流式输出已完成
@ -682,15 +685,11 @@ object AgentService : CoroutineScope {
}
override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) {
try {
sendEvent("function_call_result", mapOf(
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString()
))
FileLogger.d(TAG, "mcp调用结果: $functionCallResult")
} catch (e: Exception) {
FileLogger.e(TAG, "处理函数调用结果事件失败", e)
}
sendEvent("function_call_result", mapOf(
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
))
FileLogger.d(TAG, "mcp调用结果: $functionCallResult")
}
}
)
@ -789,7 +788,7 @@ object AgentService : CoroutineScope {
/**
* 保存聊天消息
*/
private fun saveChatMessage(userMessage: String, assistantMessage: String) {
private fun saveChatMessage(userMessage: String, assistantMessage: String,metadata : String) {
launch(Dispatchers.IO) {
try {
// 保存用户消息
@ -804,7 +803,8 @@ object AgentService : CoroutineScope {
val assistantMessageId = chatStorageHelper.saveMessage(
sessionId = sessionId,
message = assistantMessage,
sender = "assistant"
sender = "assistant",
metadata = metadata
)
// if (assistantMessageId != -1L) {
@ -985,59 +985,4 @@ object AgentService : CoroutineScope {
return true
}
/**
* 简单音频播放器,用于播放提示音等
*/
class AudioPlayer(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null
/**
* 播放音频资源
* @param resId 资源ID
*/
fun playAudio(resId: Int) {
try {
// 释放之前的资源
release()
// 创建播放器并设置资源
mediaPlayer = MediaPlayer().apply {
// 设置资源
context.resources.openRawResourceFd(resId)?.use { fd ->
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
}
// 播放完成后自动释放资源
setOnCompletionListener {
release()
}
// 准备并播放
prepare()
start()
}
FileLogger.d(TAG, "开始播放音频资源")
} catch (e: Exception) {
FileLogger.e(TAG, "播放音频资源异常: ${e.message}", e)
release()
}
}
/**
* 释放资源
*/
fun release() {
try {
mediaPlayer?.apply {
if (isPlaying) stop()
release()
}
mediaPlayer = null
} catch (e: Exception) {
// 忽略异常
}
}
}
}

1
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -57,6 +57,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
config["openaiApiKey"]?.toString()?.let { intent.putExtra("openaiApiKey", it) }
config["openaiBaseUrl"]?.toString()?.let { intent.putExtra("openaiBaseUrl", it) }
config["openaiModel"]?.toString()?.let { intent.putExtra("openaiModel", it) }
config["systemPrompt"]?.toString()?.let { intent.putExtra("systemPrompt", it) }
config["mcpServer"]?.toString()?.let { intent.putExtra("mcpServer", it) }
// 启动前台服务

3
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt

@ -54,6 +54,7 @@ class BleMediaService : Service(), CoroutineScope {
private const val KEY_OPENAI_API_KEY = "openaiApiKey"
private const val KEY_OPENAI_BASE_URL = "openaiBaseUrl"
private const val KEY_OPENAI_MODEL = "openaiModel"
private const val KEY_SYSTEM_PROMPT = "systemPrompt"
private const val KEY_MCP_SERVER = "mcpServer"
// 用于跟踪服务是否正在运行
@ -203,6 +204,7 @@ class BleMediaService : Service(), CoroutineScope {
it.getStringExtra(KEY_OPENAI_API_KEY)?.let { value -> config[KEY_OPENAI_API_KEY] = value }
it.getStringExtra(KEY_OPENAI_BASE_URL)?.let { value -> config[KEY_OPENAI_BASE_URL] = value }
it.getStringExtra(KEY_OPENAI_MODEL)?.let { value -> config[KEY_OPENAI_MODEL] = value }
it.getStringExtra(KEY_SYSTEM_PROMPT)?.let { value -> config[KEY_SYSTEM_PROMPT] = value }
it.getStringExtra(KEY_MCP_SERVER)?.let { value -> config[KEY_MCP_SERVER] = value }
}
FileLogger.i(TAG, "从Intent中提取到配置参数: $config")
@ -233,6 +235,7 @@ class BleMediaService : Service(), CoroutineScope {
KEY_OPENAI_API_KEY to getConfigString(prefs, KEY_OPENAI_API_KEY, ""),
KEY_OPENAI_BASE_URL to getConfigString(prefs, KEY_OPENAI_BASE_URL, "https://api.openai.com/v1"),
KEY_OPENAI_MODEL to getConfigString(prefs, KEY_OPENAI_MODEL, "gpt-3.5-turbo"),
KEY_SYSTEM_PROMPT to getConfigString(prefs, KEY_SYSTEM_PROMPT, "gpt-3.5-turbo"),
KEY_MCP_SERVER to getConfigString(prefs, KEY_MCP_SERVER, "")
)
}

2
local_plugins/agent_service/pubspec.yaml

@ -19,6 +19,8 @@ dependencies:
path: ../deepvoice_utils
chat_storage:
path: ../chat_storage
music_service:
path: ../music_service
get: ^4.6.5
dev_dependencies:

3
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -172,7 +172,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
}
}
"startContinuousRecognition" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
val useExternalAudio = call.argument<Boolean>("audioSourceType") ?: false
// 确保事件通道已准备好
if (asrEventSink == null) {
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null)
@ -187,6 +187,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(mapOf(

39
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -1027,8 +1027,45 @@ object BleService {
* 控制编解码 - 打开解码
*/
fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte()))
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
}
/**
* 控制编解码 - 打开解码
*/
fun openA2DPDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
}
/**

4
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt

@ -197,6 +197,10 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
"openDecoder" -> {
val success = BleService.openDecoder()
result.success(success)
}
"openA2DPDecoder" -> {
val success = BleService.openA2DPDecoder()
result.success(success)
}
"closeCodec" -> {
val success = BleService.closeCodec()

105
local_plugins/ble_service/lib/ble_service.dart

@ -15,32 +15,35 @@ class BleConnectionState {
/// BLE服务插件
class BleService {
static final BleService _instance = BleService._internal();
/// 获取单例
factory BleService() => _instance;
// 方法通道
final MethodChannel _methodChannel = const MethodChannel('com.yunqiinnovation.ble_service');
final MethodChannel _methodChannel =
const MethodChannel('com.yunqiinnovation.ble_service');
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
BleService._internal() {
_dataEventChannel = const EventChannel('com.yunqiinnovation.ble_service/data_events');
_statusEventChannel = const EventChannel('com.yunqiinnovation.ble_service/status_events');
_dataEventChannel =
const EventChannel('com.yunqiinnovation.ble_service/data_events');
_statusEventChannel =
const EventChannel('com.yunqiinnovation.ble_service/status_events');
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
@ -56,7 +59,7 @@ class BleService {
return false;
}
}
/// 获取电池电量
Future<int> getBatteryLevel() async {
try {
@ -67,18 +70,19 @@ class BleService {
return 0;
}
}
/// 获取设备版本信息
Future<bool> getDeviceVersion() async {
try {
final result = await _methodChannel.invokeMethod<bool>('getDeviceVersion');
final result =
await _methodChannel.invokeMethod<bool>('getDeviceVersion');
return result ?? false;
} catch (e) {
print('获取设备版本失败: $e');
return false;
}
}
/// 获取电池信息
Future<bool> getBatteryInfo() async {
try {
@ -89,7 +93,7 @@ class BleService {
return false;
}
}
/// 打开编码器
Future<bool> openEncoder() async {
try {
@ -100,7 +104,7 @@ class BleService {
return false;
}
}
/// 打开解码器
Future<bool> openDecoder() async {
try {
@ -111,7 +115,18 @@ class BleService {
return false;
}
}
/// 打开解码器
Future<bool> openA2DPDecoder() async {
try {
final result = await _methodChannel.invokeMethod<bool>('openA2DPDecoder');
return result ?? false;
} catch (e) {
print('打开解码器失败: $e');
return false;
}
}
/// 关闭编解码器
Future<bool> closeCodec() async {
try {
@ -122,7 +137,7 @@ class BleService {
return false;
}
}
/// 播放A2DP
Future<bool> playA2DP() async {
try {
@ -133,7 +148,7 @@ class BleService {
return false;
}
}
/// 启动BLE后台服务
/// [config] 配置参数,可包含:
/// - scan_interval: 扫描间隔时间(毫秒)
@ -144,14 +159,15 @@ class BleService {
/// - device_name: 设备名称
Future<bool> startBleService(Map<String, dynamic> config) async {
try {
final result = await _methodChannel.invokeMethod<bool>('startBleService', config);
final result =
await _methodChannel.invokeMethod<bool>('startBleService', config);
return result ?? false;
} catch (e) {
print('启动BLE后台服务失败: $e');
return false;
}
}
/// 停止BLE后台服务
Future<bool> stopBleService() async {
try {
@ -225,31 +241,31 @@ class BleService {
}
}
/// 注册BLE后台扫描
///
///
/// 注册后,即使应用被杀死,系统也会在检测到设备广播时唤醒应用
/// 这是确保设备可以在应用后台时被自动连接的关键功能
///
/// [macAddress] 可选的设备MAC地址,如果不提供则使用已关联的设备
Future<bool> registerBackgroundScan({String? macAddress}) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'registerBackgroundScan',
{'macAddress': macAddress},
);
final result =
await _methodChannel.invokeMethod<bool>('registerBackgroundScan');
return result ?? false;
} catch (e) {
print('注册后台扫描失败: $e');
return false;
}
}
/// 注销BLE后台扫描
///
///
/// 当不再需要后台扫描功能时调用,例如用户主动断开连接时
Future<bool> unregisterBackgroundScan() async {
try {
final result = await _methodChannel.invokeMethod<bool>('unregisterBackgroundScan');
final result =
await _methodChannel.invokeMethod<bool>('unregisterBackgroundScan');
return result ?? false;
} catch (e) {
print('注销后台扫描失败: $e');
@ -269,13 +285,13 @@ class BleService {
return false;
}
}
//========================================================================
// 以下是ChatAgent相关功能
//========================================================================
/// 初始化语音交互代理
///
///
/// [config] 配置参数,必须包含:
/// - azureSpeechKey: Azure语音服务API密钥
/// - azureSpeechRegion: Azure语音服务区域
@ -284,16 +300,17 @@ class BleService {
/// - openaiModel: OpenAI模型名称,默认为'gpt-3.5-turbo'
Future<bool> initializeChatAgent(Map<String, dynamic> config) async {
try {
final result = await _methodChannel.invokeMethod<bool>('initializeChatAgent', config);
final result = await _methodChannel.invokeMethod<bool>(
'initializeChatAgent', config);
return result ?? false;
} catch (e) {
print('初始化语音交互代理失败: $e');
return false;
}
}
/// 启动语音识别
///
///
/// 用于手动启动语音识别,通常不需要直接调用,因为在唤醒时会自动启动
Future<bool> startListening() async {
try {
@ -304,7 +321,7 @@ class BleService {
return false;
}
}
/// 停止语音识别
Future<bool> stopListening() async {
try {
@ -315,14 +332,16 @@ class BleService {
return false;
}
}
/// 处理文本输入
///
///
/// [text] 用户输入的文本
/// [speakResponse] 是否使用TTS朗读回复,默认为false
Future<bool> processTextInput(String text, {bool speakResponse = false}) async {
Future<bool> processTextInput(String text,
{bool speakResponse = false}) async {
try {
final result = await _methodChannel.invokeMethod<bool>('processTextInput', {
final result =
await _methodChannel.invokeMethod<bool>('processTextInput', {
'text': text,
'speakResponse': speakResponse,
});
@ -332,7 +351,7 @@ class BleService {
return false;
}
}
/// 停止TTS播放
Future<bool> stopTts() async {
try {
@ -343,4 +362,4 @@ class BleService {
return false;
}
}
}
}

33
local_plugins/music_service/.gitignore

@ -0,0 +1,33 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/

33
local_plugins/music_service/.metadata

@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: android
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: ios
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
local_plugins/music_service/CHANGELOG.md

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
local_plugins/music_service/LICENSE

@ -0,0 +1 @@
TODO: Add your license here.

15
local_plugins/music_service/README.md

@ -0,0 +1,15 @@
# music_service
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

4
local_plugins/music_service/analysis_options.yaml

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

9
local_plugins/music_service/android/.gitignore

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

70
local_plugins/music_service/android/build.gradle

@ -0,0 +1,70 @@
group = "com.yunqiinnovation.music_service"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "1.8.22"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "com.yunqiinnovation.music_service"
compileSdk = 35
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 21
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
dependencies {
implementation project(':deepvoice_utils')
}

1
local_plugins/music_service/android/settings.gradle

@ -0,0 +1 @@
rootProject.name = 'music_service'

17
local_plugins/music_service/android/src/main/AndroidManifest.xml

@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yunqiinnovation.music_service">
<!-- 基础前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 及以上需要声明具体的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application>
<service
android:name=".MusicService"
android:exported="false"
android:enabled="true"
android:foregroundServiceType="mediaPlayback" />
</application>
</manifest>

204
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -0,0 +1,204 @@
package com.yunqiinnovation.music_service
import android.app.*
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger
import java.util.concurrent.atomic.AtomicBoolean
class MusicService : Service() {
companion object {
private const val TAG = "MusicService"
private const val CHANNEL_ID = "music_media_service_channel"
private const val NOTIFICATION_ID = 1002
private val isRunning = AtomicBoolean(false)
private var currentStatus: Boolean = false
private var currentUrlInternal: String? = null
fun isServiceRunning(): Boolean = isRunning.get()
fun isPlaying(): Boolean = currentStatus
fun getCurrentUrl(): String? = currentUrlInternal
}
private var mediaPlayer: MediaPlayer? = null
private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲
private var currentIndex = 0
override fun onCreate() {
super.onCreate()
FileLogger.init(this)
FileLogger.i(TAG, "MusicService 正在创建...")
isRunning.set(true)
Log.i(TAG, "MusicService Created")
startForeground(NOTIFICATION_ID, createNotification("等待播放..."))
isRunning.set(true)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY
Log.i(TAG, "onStartCommand: $command" )
when (command) {
"play" -> {
val url = intent.getStringExtra("url") ?: return START_NOT_STICKY
if (!playlist.contains(url)) playlist.add(url)
currentIndex = playlist.indexOf(url)
play(url)
}
"playlist" -> {
val urls = intent.getStringArrayListExtra("urls")?.filter { it.isNotEmpty() } ?: return START_NOT_STICKY
Log.i(TAG, "后台服务播放 playlist: $urls" )
if (urls.isNotEmpty()) {
playlist.clear()
playlist.addAll(urls)
currentIndex = 0
play(playlist[currentIndex])
} else {
Log.w(TAG, "playlist 命令收到空列表")
}
}
"pause" -> pause()
"resume" -> resume()
"stop" -> stopSelf()
"next" -> {
if (currentIndex + 1 < playlist.size) {
currentIndex++
play(playlist[currentIndex])
}
}
"previous" -> {
if (currentIndex - 1 >= 0) {
currentIndex--
play(playlist[currentIndex])
}
}
"setVolume" -> {
val volume = intent.getFloatExtra("volume", 1.0f).coerceIn(0f, 1f)
mediaPlayer?.setVolume(volume, volume)
}
}
return START_STICKY
}
private fun play(url: String) {
Log.i(TAG, "播放音乐: $url")
releaseMediaPlayer()
currentStatus = true;
currentUrlInternal = url;
mediaPlayer = MediaPlayer().apply {
setDataSource(url)
setAudioStreamType(AudioManager.STREAM_MUSIC)
setOnPreparedListener {
start()
currentStatus = true
updateNotification("正在播放")
Log.i(TAG, "开始播放")
}
setOnCompletionListener {
Log.i(TAG, "播放完成")
// 播放下一首
if (currentIndex + 1 < playlist.size) {
currentIndex++
play(playlist[currentIndex]) // 播放下一首
} else {
currentStatus = false
stopSelf() // 如果没有更多歌曲了,可以停止服务
}
}
setOnErrorListener { _, what, extra ->
Log.e(TAG, "播放错误: what=$what extra=$extra")
currentStatus = false
stopSelf()
true
}
prepareAsync()
}
}
private fun pause() {
mediaPlayer?.let {
if (it.isPlaying) {
it.pause()
currentStatus= false
updateNotification("已暂停")
Log.i(TAG, "暂停播放")
}
}
}
private fun resume() {
mediaPlayer?.let {
if (!it.isPlaying) {
it.start()
currentStatus = true
updateNotification("继续播放")
Log.i(TAG, "继续播放")
}
}
}
private fun releaseMediaPlayer() {
mediaPlayer?.apply {
stop()
release()
}
mediaPlayer = null
}
private fun updateNotification(contentText: String) {
val notification = createNotification(contentText)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.notify(NOTIFICATION_ID, notification)
}
private fun createNotification(contentText: String): Notification {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"音乐播放服务",
NotificationManager.IMPORTANCE_LOW
)
manager.createNotificationChannel(channel)
}
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val pendingIntent = intent?.let {
PendingIntent.getActivity(
this, 0, it,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("DeepSound")
.setContentText(contentText)
.setSmallIcon(android.R.drawable.ic_media_play)
.setContentIntent(pendingIntent)
.setOngoing(true)
.build()
}
override fun onDestroy() {
Log.i(TAG, "服务销毁,释放播放器")
releaseMediaPlayer()
stopForeground(true)
isRunning.set(false)
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}

59
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

@ -0,0 +1,59 @@
package com.yunqiinnovation.music_service
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
/** MusicServicePlugin */
class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
private val TAG = "MusicServicePlugin"
private lateinit var context: Context
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
FileLogger.d(TAG, "Method call: ${call.method}")
val intent = Intent(context, MusicService::class.java)
when (call.method) {
"play" -> {
val url = call.argument<String>("url")
MusicServiceStarter.startServiceWithCommand(context, "play", url)
}
"playlist" -> {
val urls = call.argument<List<String>>("urls")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls)
}
"pause" -> MusicServiceStarter.startServiceWithCommand(context, "pause")
"resume" -> MusicServiceStarter.startServiceWithCommand(context, "resume")
"stop" -> MusicServiceStarter.startServiceWithCommand(context, "stop")
"setVolume" -> {
val volume = call.argument<Double>("volume")?.toFloat()
MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume)
}
"getStatus" -> {
val isPlaying = MusicService.isPlaying()
val currentUrl = MusicService.getCurrentUrl()
val status = mapOf(
"isPlaying" to isPlaying,
"url" to currentUrl
)
result.success(status)
}
else -> result.notImplemented()
}
result.success(null)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
}

30
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt

@ -0,0 +1,30 @@
package com.yunqiinnovation.music_service
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.content.ContextCompat
object MusicServiceStarter {
private val TAG = "MusicServiceStarter"
//播放单曲
fun startServiceWithCommand(context: Context, command: String, url: String? = null, volume: Float? = null) {
Log.i(TAG, "startServiceWithCommand: $command $url")
val intent = Intent(context, MusicService::class.java).apply {
putExtra("command", command)
url?.let { putExtra("url", it) }
volume?.let { putExtra("volume", it) }
}
ContextCompat.startForegroundService(context, intent)
}
//启动播放列表
fun startServiceWithPlaylist(context: Context,command: String, urls: List<String>?,volume: Float? = null) {
Log.i(TAG, "startServiceWithPlaylist: $urls")
val intent = Intent(context, MusicService::class.java).apply {
putExtra("command", command)
putStringArrayListExtra("urls", ArrayList(urls!!)) // 注意类型是 ArrayList<String>
}
ContextCompat.startForegroundService(context, intent)
}
}

38
local_plugins/music_service/ios/.gitignore

@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

0
local_plugins/music_service/ios/Assets/.gitkeep

19
local_plugins/music_service/ios/Classes/MusicServicePlugin.swift

@ -0,0 +1,19 @@
import Flutter
import UIKit
public class MusicServicePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "music_service", binaryMessenger: registrar.messenger())
let instance = MusicServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

14
local_plugins/music_service/ios/Resources/PrivacyInfo.xcprivacy

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

29
local_plugins/music_service/ios/music_service.podspec

@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint music_service.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'music_service'
s.version = '0.0.1'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
# If your plugin requires a privacy manifest, for example if it uses any
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
# plugin's privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'music_service_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

62
local_plugins/music_service/lib/music_service.dart

@ -0,0 +1,62 @@
import 'package:flutter/services.dart';
class MusicService {
static final MusicService _instance = MusicService._internal();
/// 获取单例
factory MusicService() => _instance;
// 方法通道
final MethodChannel _methodChannel = const MethodChannel(
'com.yunqiinnovation.music_service',
);
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
MusicService._internal() {
_dataEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/data_events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/status_events',
);
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
Future<void> playMusic(String url) async {
await _methodChannel.invokeMethod('play', {'url': url});
}
Future<void> pauseMusic() async => _methodChannel.invokeMethod('pause');
Future<void> resumeMusic() async => _methodChannel.invokeMethod('resume');
Future<void> nextTrack() async => _methodChannel.invokeMethod('next');
Future<void> previousTrack() async => _methodChannel.invokeMethod('previous');
Future<void> stopMusic() async => _methodChannel.invokeMethod('stop');
Future<void> setVolume(double volume) async => _methodChannel.invokeMethod(
'setVolume',
{'volume': volume},
); // volume: 0.0~1.0
Future<Map<String, dynamic>> getStatus() async {
final result = await _methodChannel.invokeMethod<Map>('getStatus');
return {
'isPlaying': result?['isPlaying'] ?? false,
'url': result?['url'] ?? "",
};
}
}

72
local_plugins/music_service/pubspec.yaml

@ -0,0 +1,72 @@
name: music_service
description: "A new Flutter project."
version: 0.0.1
homepage:
environment:
sdk: ^3.7.2
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.yunqiinnovation.music_service
pluginClass: MusicServicePlugin
ios:
pluginClass: MusicServicePlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

2
pubspec.yaml

@ -86,6 +86,8 @@ dependencies:
path: local_plugins/classic_bluetooth
agent_service:
path: local_plugins/agent_service
music_service:
path: local_plugins/music_service
dev_dependencies:

Loading…
Cancel
Save