Browse Source

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

weicu
tanlongsheng 1 year ago
parent
commit
339bc7a41b
  1. 1
      .gitignore
  2. 2
      lib/core/themes/app_theme.dart
  3. 192
      lib/core/translations/language/en_us.dart
  4. 91
      lib/core/translations/language/zh_cn.dart
  5. 7
      lib/data/models/appconfig.dart
  6. 24
      lib/data/models/appconfig_model.dart
  7. 16
      lib/data/models/appconfig_model.g.dart
  8. 5
      lib/data/models/user_Info.dart
  9. 4
      lib/data/services/navigation_manager.dart
  10. 13
      lib/data/services/network/api.dart
  11. 4
      lib/data/services/network/nw_api.dart
  12. 27
      lib/modules/agent/views/music_card.dart
  13. 436
      lib/modules/agent/views/navigation_card.dart
  14. 110
      lib/modules/agent/views/travel_card.dart
  15. 213
      lib/modules/agent/views/weather_card.dart
  16. 14
      lib/modules/devices/controllers/devices_controller.dart
  17. 6
      lib/modules/devices/views/devices_view.dart
  18. 6
      lib/modules/home/controllers/home_controller.dart
  19. 6
      lib/modules/home/views/home_view.dart
  20. 5
      lib/modules/login/controllers/login_controller.dart
  21. 20
      lib/modules/login/views/login_view.dart
  22. 109
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  23. 310
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  24. 27
      lib/modules/pairing/controllers/pairing_controller.dart
  25. 7
      lib/modules/settings/controllers/settings_controller.dart
  26. 49
      lib/modules/settings/views/settings_view.dart
  27. 175
      lib/modules/translation/controllers/translation_controller.dart
  28. 188
      lib/modules/translation/views/translation_detail_view.dart
  29. 91
      lib/modules/translation/views/translation_history_view.dart
  30. 344
      lib/modules/translation/views/translation_view.dart
  31. 14
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  32. 144
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  33. 102
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt

1
.gitignore

@ -220,3 +220,4 @@ fastlane/screenshots/**/*.png
fastlane/test_output fastlane/test_output
fastlane/readme.md fastlane/readme.md
.trae/rules/project_rules.md .trae/rules/project_rules.md
.augment/rules/imported/project_rules.md

2
lib/core/themes/app_theme.dart

@ -69,4 +69,4 @@ class AppTheme {
), ),
), ),
); );
} }

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

@ -46,6 +46,26 @@ const Map<String, String> enUS = {
"malay": "Malay", "malay": "Malay",
"turkish": "Turkish", "turkish": "Turkish",
"polish": "Polish", "polish": "Polish",
"filipino": "Filipino",
"dutch": "Dutch",
"swedish": "Swedish",
"danish": "Danish",
"finnish": "Finnish",
"czech": "Czech",
"hungarian": "Hungarian",
"norwegian": "Norwegian",
"romanian": "Romanian",
"ukrainian": "Ukrainian",
"greek": "Greek",
"bulgarian": "Bulgarian",
"croatian": "Croatian",
"slovak": "Slovak",
"slovenian": "Slovenian",
"lithuanian": "Lithuanian",
"latvian": "Latvian",
"estonian": "Estonian",
"serbian": "Serbian",
"icelandic": "Icelandic",
// 登录页 // 登录页
"welcome": "Welcome", "welcome": "Welcome",
@ -68,10 +88,14 @@ const Map<String, String> enUS = {
"invalidPhoneFormat": "Invalid phone number format", "invalidPhoneFormat": "Invalid phone number format",
"tip": "Tip", "tip": "Tip",
"pleaseEnterCompleteCode": "Please enter the complete verification code", "pleaseEnterCompleteCode": "Please enter the complete verification code",
// 登录错误提示
"loginFailed": "Login failed, please try again", "loginFailed": "Login failed, please try again",
'pleaseAcceptPrivacyPolicy': 'pleaseAcceptPrivacyPolicy':
'Please read and accept the Privacy Policy and User Agreement first', 'Please read and accept the Privacy Policy and User Agreement first',
"enterVerificationCode": "Enter verification code",
"verificationCodeSentTo": "Verification code sent to",
"resendVerificationCode": "Resend verification code",
"login": "Login",
"usePasswordLogin": "Use password login",
// 首页 // 首页
"deviceStatus": "Device Status", "deviceStatus": "Device Status",
@ -116,6 +140,12 @@ const Map<String, String> enUS = {
"unknown": "Unknown", "unknown": "Unknown",
'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on', 'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on',
'bluetoothOff': 'bluetooth is off', 'bluetoothOff': 'bluetooth is off',
'leftEar': 'Left Ear',
'rightEar': 'Right Ear',
'chargingCase': 'Charging Case',
'left': 'Left',
'right': 'Right',
'checkConnectionStatusFailed': 'Failed to check connection status',
// 发现页 // 发现页
"searchAi": "Search AI features...", "searchAi": "Search AI features...",
@ -127,6 +157,9 @@ const Map<String, String> enUS = {
"allRoundAssistant": "All-round Assistant", "allRoundAssistant": "All-round Assistant",
"allRoundAssistantInfo": "Helps solve various problems", "allRoundAssistantInfo": "Helps solve various problems",
"simultaneousTranslation": "Real-time Translation", "simultaneousTranslation": "Real-time Translation",
"faceToFaceTranslation": "Face-to-face Translation",
'audioVideoTranslation': 'Audio/Video Translation',
'callTranslation': 'Call Translation',
"simultaneousTranslationInfo": "Instant translation for 80+ languages", "simultaneousTranslationInfo": "Instant translation for 80+ languages",
"aiGirlfriend": "Parent-child bondingn", "aiGirlfriend": "Parent-child bondingn",
"aiGirlfriendInfo": "Intelligent company, chat at any time", "aiGirlfriendInfo": "Intelligent company, chat at any time",
@ -165,6 +198,7 @@ const Map<String, String> enUS = {
'meetingTranscript': 'Meeting Transcript', 'meetingTranscript': 'Meeting Transcript',
// 设置页 // 设置页
"systemSettings": "System Settings",
"premiumMember": "Premium Membership", "premiumMember": "Premium Membership",
"joinDevice": "Connected device:", "joinDevice": "Connected device:",
"accountId": "Account ID:", "accountId": "Account ID:",
@ -177,6 +211,8 @@ const Map<String, String> enUS = {
"lastBackup": "Last backup:", "lastBackup": "Last backup:",
"privacySettings": "Privacy Settings", "privacySettings": "Privacy Settings",
"privacySettingsInfo": "Manage data collection and permissions", "privacySettingsInfo": "Manage data collection and permissions",
"permissionManagement": "Permission Management", // 权限管理
"permissionManagementInfo": "Manage app permission settings", // 管理应用权限设置
"logout": "Log Out", "logout": "Log Out",
"logoutTips": "Are you sure you want to log out of the current account?", "logoutTips": "Are you sure you want to log out of the current account?",
"aboutApplication": "About", "aboutApplication": "About",
@ -204,6 +240,16 @@ const Map<String, String> enUS = {
'deleteAccount': 'Delete Account', 'deleteAccount': 'Delete Account',
'deleteAccountTips': 'deleteAccountTips':
'Are you sure you want to permanently delete your account? This action cannot be undone.', 'Are you sure you want to permanently delete your account? This action cannot be undone.',
"firmwareUpgrade": "Firmware Upgrade", // 固件升级
"bleHeadsetFirmwareUpgrade": "BLE Headset Firmware Upgrade", // BLE耳机固件版本升级
"deviceManagement": "Device Management", // 设备管理
"manageConnectedDevices": "Manage Connected Bluetooth Devices", // 管理已绑定的蓝牙设备
// 设置页面 - 实时语音对话
'realtimeVoiceChat': 'Real-time Voice Chat',
'realtimeVoiceChatInfo':
'Experience intelligent voice interaction features (Preview)',
'loading': 'Loading...',
// 聊天页 // 聊天页
"clearChatHistory": "Clear Chat History", "clearChatHistory": "Clear Chat History",
@ -212,16 +258,12 @@ const Map<String, String> enUS = {
"imListening": "Listening...", "imListening": "Listening...",
"canStartTalking": "You may start speaking", "canStartTalking": "You may start speaking",
// 翻译页 // 编辑个人信息页
"selectSourceLanguage": "Select Source Language",
"selectTargetLanguage": "Select Target Language",
//编辑个人信息页
"editProfile": "Edit Profile", "editProfile": "Edit Profile",
"nickname": "Nickname", "nickname": "Nickname",
"save": "Save", "save": "Save",
//国家列表 // 国家列表
"邮箱": "Email", "邮箱": "Email",
"中国": "China", "中国": "China",
"美国": "United States", "美国": "United States",
@ -329,51 +371,109 @@ const Map<String, String> enUS = {
"斐济": "Fiji", "斐济": "Fiji",
"所罗门群岛": "Solomon Islands", "所罗门群岛": "Solomon Islands",
// Personal Assistant Emma // 导航
"personalAssistantName": "Emma", "driving": "Driving",
"personalAssistantDescription": "walking": "Walking",
"Hi, I'm Emma, a versatile AI assistant. Whether it's work planning, study advice, life arrangements, or any other issues, I'm happy to help. Let's improve efficiency and achieve goals together!", "bicycling": "Cycling",
"assistantTag": "Assistant", "tsransit": "Transit",
"personalAssistantWelcome":
"Hello! I'm Emma, and I'm happy to be your personal assistant. I can help you with various tasks such as time management, goal planning, information organization, etc. What can I help you with?",
"personalAssistantVoiceWelcome": "I'm here!",
// 同声传译 Lucy // 翻译模块
"interpreterName": "Simultaneous Interpreter", // 翻译主页
"interpreterDescription": "translationHistory": "Translation History",
"Hi! I'm Lucy, a professional English-Chinese interpreter specializing in real-time translation for meetings, lectures, and conversations, making communication barrier-free.", "enableRecording": "Enable Recording",
"interpreterTag": "Translation", "viewAudioFiles": "View Audio Files",
"interpreterWelcome": "headsetUser": "Headset User",
"Hello everyone! I'm Lucy, your simultaneous interpreter. I'll translate between Chinese and English for you. When you speak Chinese, I'll translate to English; when you speak English, I'll translate to Chinese. Let's start our translation session!", "phoneUser": "Phone User",
"selectSourceLanguage": "Select Source Language",
// ... existing code ... "selectTargetLanguage": "Select Target Language",
// 亲子陪伴 芯愿
"companionName": "XinYuan",
"companionDescription":
"I am XinYuan, your dedicated parent-child companion, gentle and understanding. Whether you want to chat, share concerns, or need companionship, I'll always be by your side.",
"companionTag": "Emotional",
"companionWelcome":
"Hello! Finally meeting you! I'm XinYuan, your dedicated parent-child companion. How was your day? Is there anything you'd like to share with me? I'm always here, ready to listen to your thoughts.",
"companionVoiceWelcome": "I'm here with you!",
// 多功能AI聊天 吴小江
"healthAdvisorName": "AI Chat Assistant Wu",
"healthAdvisorDescription":
"I'm Wu, ready to provide you with various AI chat features.",
"healthAdvisorTag": "Chat",
"healthAdvisorWelcome":
"Hello, I'm Wu. I'm happy to assist you with AI chat features. How may I help you today?",
"healthAdvisorVoiceWelcome": "I'm here!",
// Translation History // 翻译历史页
"noTranslationHistory": "No translation history", "noTranslationHistory": "No translation history",
"clearHistory": "Clear History", "clearHistory": "Clear History",
"clearHistoryConfirm": "clearHistoryConfirm":
"Are you sure you want to clear all translation history? This action cannot be undone.", "Are you sure you want to clear all translation history? This action cannot be undone.",
"translationHistory": "Translation History", "unknownLanguage": "Unknown Language",
"deleteSessionRecord": "Delete Session Record",
"deleteSessionConfirm":
"Are you sure you want to delete all translation records for this session? This action cannot be undone.",
// 翻译音频文件列表页
"translateAudioFiles": "Translate Audio Files",
"noAudioFiles": "No audio files",
"calculating": "Calculating...",
"playing": "Playing...",
"paused": "Paused",
"clickToPlay": "Click to play",
"rename": "Rename",
"smartTranscription": "Smart Transcription",
"renameAudioFile": "Rename Audio File",
"enterNewFileName": "Please enter new file name",
// 翻译详情页
"originalRecord": "Original Record",
"recordSummary": "Record Summary",
"clickToGenerateSummary":
"Click the button below to generate session summary",
"summaryContent": "Summary Content",
"copied": "Copied",
"summaryCopiledToClipboard": "Summary copied to clipboard",
"extractSummary": "Extract Summary",
"summaryGenerated": "Summary generated",
"generating": "Generating...",
"regenerate": "Regenerate",
"summaryTimeoutMessage":
"Summary generation request timed out, possibly due to network issues or server busy.",
"deleteTranslationRecord": "Delete Translation Record",
"deleteTranslationConfirm":
"Are you sure you want to delete this translation record? This action cannot be undone.",
"originalText": "Original Text",
"translatedText": "Translated Text",
"sessionSummary": "Session Summary",
"cannotShare": "Cannot Share",
"generateSummaryFirst": "Please generate summary first",
"translationSessionRecord": "Translation Session Record",
// 配对模块
// 配对页面
"devicePairing": "Device Pairing",
"connectYourDeepSoundHeadset": "Connect Your DeepSound Headset",
"bind": "Bind",
"authorizeBind": "Authorize Binding",
"enterCode": "Please enter code",
"autoInputCode": "Auto Input Code",
"skipBinding": "Skip Binding",
"rescan": "Rescan",
"skip": "Skip",
"verifyingDevice": "Verifying device...",
"confirmBinding": "Confirm Binding",
"associatingDevice": "Associating device...",
"goToBluetoothSettings": "Go to Bluetooth Settings",
// 配对状态文本
"readyToPair": "Ready to pair",
"preparingPairingEnvironment": "Preparing pairing environment...",
"connectDeepSoundViaSystemBluetooth":
"Please connect DeepSound device via system Bluetooth first",
"deviceBindingConfirm": "Device binding confirmation...",
"establishingDeviceAssociation": "Establishing device association...",
"waitingCompletePairing": "Please wait, completing pairing...",
"pairingSuccessRedirecting":
"Pairing successful! Redirecting to home page...",
// 配对错误状态文本
"ensureConnectedDeepSoundDevice":
"Please ensure connected device is DeepSound",
"deviceAlreadyBoundToOtherAccount":
"This device is already bound to another account",
"deviceAssociationFailed": "Device association failed, please retry",
"scanTimeoutEnsureDeviceNearby":
"Scan timeout, please ensure device is nearby",
"networkError": "Network error",
"authorizationFailedCheckCode":
"Authorization failed, please check authorization code",
"pairingFailedRetry": "Pairing failed, please retry",
// User Feedback Page // 用户反馈页
"feedback": "Feedback", "feedback": "Feedback",
'feedbackInfo': 'Provide feedback or suggestions', 'feedbackInfo': 'Provide feedback or suggestions',
'feedbackDescription': 'Please describe your issue or suggestion', 'feedbackDescription': 'Please describe your issue or suggestion',
@ -385,7 +485,7 @@ const Map<String, String> enUS = {
'feedbackSubmitSuccess': 'Feedback submitted successfully, thank you!', 'feedbackSubmitSuccess': 'Feedback submitted successfully, thank you!',
'feedbackSubmitFailed': 'Failed to submit feedback, please try again later', 'feedbackSubmitFailed': 'Failed to submit feedback, please try again later',
// Language names // 语言名称
"中文(简体)": "Chinese (Simplified)", "中文(简体)": "Chinese (Simplified)",
"中文(繁体)": "Chinese (Traditional)", "中文(繁体)": "Chinese (Traditional)",
"中文(香港繁体)": "Chinese (Hong Kong)", "中文(香港繁体)": "Chinese (Hong Kong)",

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

@ -46,6 +46,26 @@ const Map<String, String> zhCN = {
"malay": "马来语", "malay": "马来语",
"turkish": "土耳其语", "turkish": "土耳其语",
"polish": "波兰语", "polish": "波兰语",
"filipino": "菲律宾语",
"dutch": "荷兰语",
"swedish": "瑞典语",
"danish": "丹麦语",
"finnish": "芬兰语",
"czech": "捷克语",
"hungarian": "匈牙利语",
"norwegian": "挪威语",
"romanian": "罗马尼亚语",
"ukrainian": "乌克兰语",
"greek": "希腊语",
"bulgarian": "保加利亚语",
"croatian": "克罗地亚语",
"slovak": "斯洛伐克语",
"slovenian": "斯洛文尼亚语",
"lithuanian": "立陶宛语",
"latvian": "拉脱维亚语",
"estonian": "爱沙尼亚语",
"serbian": "塞尔维亚语",
"icelandic": "冰岛语",
// 登录页 // 登录页
"welcome": "欢迎使用", "welcome": "欢迎使用",
@ -69,6 +89,11 @@ const Map<String, String> zhCN = {
"pleaseEnterCompleteCode": "请输入完整的验证码", "pleaseEnterCompleteCode": "请输入完整的验证码",
"loginFailed": "登录失败,请重试", "loginFailed": "登录失败,请重试",
'pleaseAcceptPrivacyPolicy': '请先阅读并同意隐私政策和用户协议', 'pleaseAcceptPrivacyPolicy': '请先阅读并同意隐私政策和用户协议',
"enterVerificationCode": "输入验证码",
"verificationCodeSentTo": "验证码已发送至",
"resendVerificationCode": "重新发送验证码",
"login": "登录",
"usePasswordLogin": "使用密码登录",
// 首页 // 首页
"deviceStatus": "设备状态", "deviceStatus": "设备状态",
@ -112,8 +137,14 @@ const Map<String, String> zhCN = {
"unknown": "未知", "unknown": "未知",
'bluetoothTurnedOff': '蓝牙已关闭,请打开蓝牙', 'bluetoothTurnedOff': '蓝牙已关闭,请打开蓝牙',
'bluetoothOff': '蓝牙已关闭', 'bluetoothOff': '蓝牙已关闭',
'leftEar': '左耳',
'rightEar': '右耳',
'chargingCase': '充电仓',
'left': '左',
'right': '右',
'checkConnectionStatusFailed': '检查连接状态失败',
// 发现页 // 发现页
"searchAi": "搜索AI功能...", "searchAi": "搜索AI功能...",
"exploreSlogan": "智能耳机,无限可能", "exploreSlogan": "智能耳机,无限可能",
"exploreSlogan1": "智能陪伴,无限可能", "exploreSlogan1": "智能陪伴,无限可能",
@ -162,6 +193,7 @@ const Map<String, String> zhCN = {
'meetingTranscript': '会议原文', 'meetingTranscript': '会议原文',
// 设置页 // 设置页
"systemSettings": "系统设置",
"premiumMember": "高级会员", "premiumMember": "高级会员",
"joinDevice": "已连接设备:", "joinDevice": "已连接设备:",
"accountId": "账号ID:", "accountId": "账号ID:",
@ -174,6 +206,8 @@ const Map<String, String> zhCN = {
"lastBackup": "上次备份:", "lastBackup": "上次备份:",
"privacySettings": "隐私设置", "privacySettings": "隐私设置",
"privacySettingsInfo": "管理数据收集和权限", "privacySettingsInfo": "管理数据收集和权限",
"permissionManagement": "权限管理", // 权限管理
"permissionManagementInfo": "管理应用权限设置", // 管理应用权限设置
"logout": "退出登录", "logout": "退出登录",
"logoutTips": "确定要退出当前账号吗?", "logoutTips": "确定要退出当前账号吗?",
"aboutApplication": "关于应用", "aboutApplication": "关于应用",
@ -198,6 +232,15 @@ const Map<String, String> zhCN = {
'completeRecording': '完成录制', 'completeRecording': '完成录制',
'deleteAccount': '注销账号', 'deleteAccount': '注销账号',
'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销', 'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销',
"firmwareUpgrade": "固件升级",
"bleHeadsetFirmwareUpgrade": "BLE耳机固件版本升级",
"deviceManagement": "设备管理",
"manageConnectedDevices": "管理已绑定的蓝牙设备",
// 设置页面 - 实时语音对话
"realtimeVoiceChat": "实时语音对话",
"realtimeVoiceChatInfo": "体验智能语音交互功能(功能预览)",
"loading": "加载中...",
// 聊天页 // 聊天页
"clearChatHistory": "清空聊天记录", "clearChatHistory": "清空聊天记录",
@ -206,10 +249,6 @@ const Map<String, String> zhCN = {
"imListening": "正在聆听...", "imListening": "正在聆听...",
"canStartTalking": "你可以开始说话", "canStartTalking": "你可以开始说话",
// 翻译页
"selectSourceLanguage": "选择源语言",
"selectTargetLanguage": "选择目标语言",
//编辑个人信息页 //编辑个人信息页
"editProfile": "编辑个人信息", "editProfile": "编辑个人信息",
"nickname": "昵称", "nickname": "昵称",
@ -323,47 +362,12 @@ const Map<String, String> zhCN = {
"斐济": "斐济", "斐济": "斐济",
"所罗门群岛": "所罗门群岛", "所罗门群岛": "所罗门群岛",
// 个人助理 Emma
"personalAssistantName": "个人助理 Emma",
"personalAssistantDescription":
"你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!",
"assistantTag": "助理",
"personalAssistantWelcome":
"你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
"personalAssistantVoiceWelcome": "我在呢!",
// 导航 // 导航
"driving": "驾车", "driving": "驾车",
"walking": "步行", "walking": "步行",
"bicycling": "骑行", "bicycling": "骑行",
"tsransit": "公交", "tsransit": "公交",
// 同声传译 Lucy
"interpreterName": "同声传译",
"interpreterDescription":
"Hello!我是 Lucy,一名专业的中英同声翻译官,擅长在会议、讲座、对话场景中进行实时翻译,让沟通无障碍。",
"interpreterTag": "翻译",
"interpreterWelcome":
"大家好!我是同声翻译官 Lucy,为你在中英之间提供实时翻译。你说中文,我就给你翻译成英文;你说英文,我就给你翻译成中文。现在,开始我们的翻译吧!",
// ... existing code ...
// 亲子陪伴 芯愿
"companionName": "亲子陪伴 芯愿",
"companionDescription":
"我是你的专属亲子陪伴芯愿,温柔体贴,善解人意。无论你想聊天、倾诉烦恼,还是需要陪伴,我都会一直在你身边。",
"companionTag": "情感",
"companionWelcome":
"您好呀,终于等到你啦!我是芯愿,你的专属亲子陪伴。今天过得怎么样?有什么想和我分享的吗?我一直都在这里,随时倾听你的心声。",
"companionVoiceWelcome": "我在这里陪着你!",
// 多功能AI聊天 吴小江
"healthAdvisorName": "多功能AI聊天 吴小江",
"healthAdvisorDescription": "我是吴小江,我可以为你提供更多AI聊天功能。",
"healthAdvisorTag": "健康",
"healthAdvisorWelcome": "您好,我是吴小江,很高兴能为您提供多功能AI聊天。我将用专业知识帮助您。请问有什么问题想咨询吗?",
"healthAdvisorVoiceWelcome": "我在这里!",
// 翻译模块 // 翻译模块
// 翻译主页 (translation_view.dart) // 翻译主页 (translation_view.dart)
"translationHistory": "翻译历史", "translationHistory": "翻译历史",
@ -371,6 +375,8 @@ const Map<String, String> zhCN = {
"viewAudioFiles": "查看录音文件", "viewAudioFiles": "查看录音文件",
"headsetUser": "耳机用户", "headsetUser": "耳机用户",
"phoneUser": "手机用户", "phoneUser": "手机用户",
"selectSourceLanguage": "选择源语言",
"selectTargetLanguage": "选择目标语言",
// 翻译历史页 (translation_history_view.dart) // 翻译历史页 (translation_history_view.dart)
"noTranslationHistory": "暂无历史记录", "noTranslationHistory": "暂无历史记录",
@ -424,12 +430,15 @@ const Map<String, String> zhCN = {
"skipBinding": "跳过绑定", "skipBinding": "跳过绑定",
"rescan": "重新扫描", "rescan": "重新扫描",
"skip": "跳过", "skip": "跳过",
"verifyingDevice": "正在验证设备...",
"confirmBinding": "确认绑定",
"associatingDevice": "正在关联设备...",
"goToBluetoothSettings": "前往蓝牙设置",
// 配对状态文本 // 配对状态文本
"readyToPair": "准备开始配对", "readyToPair": "准备开始配对",
"preparingPairingEnvironment": "正在准备配对环境...", "preparingPairingEnvironment": "正在准备配对环境...",
"connectDeepSoundViaSystemBluetooth": "请先通过系统蓝牙连接DeapSound设备", "connectDeepSoundViaSystemBluetooth": "请先通过系统蓝牙连接DeapSound设备",
"verifyingDevice": "正在验证设备...",
"deviceBindingConfirm": "设备绑定确认...", "deviceBindingConfirm": "设备绑定确认...",
"establishingDeviceAssociation": "正在建立设备关联...", "establishingDeviceAssociation": "正在建立设备关联...",
"waitingCompletePairing": "请稍候,正在完成配对...", "waitingCompletePairing": "请稍候,正在完成配对...",

7
lib/data/models/appconfig.dart

@ -4,7 +4,6 @@ import 'package:deep_voice/data/models/appconfig_model.dart';
class AppConfig { class AppConfig {
static late UserGetAppConfigResp config; static late UserGetAppConfigResp config;
// 可选:提供初始化方法 // 可选:提供初始化方法
static void initialize(UserGetAppConfigResp resp) { static void initialize(UserGetAppConfigResp resp) {
config = resp; config = resp;
@ -25,4 +24,10 @@ class AppConfig {
final foundAgent = config.agents.firstWhere((agent) => agent.id == agentId); final foundAgent = config.agents.firstWhere((agent) => agent.id == agentId);
return foundAgent.systemPrompt; return foundAgent.systemPrompt;
} }
static List<String> allproductNames() {
List<String> names =
config.products.map((user) => user.devicename).toList();
return names;
}
} }

24
lib/data/models/appconfig_model.dart

@ -7,8 +7,13 @@ class UserGetAppConfigResp {
final Map<String, String> env; final Map<String, String> env;
final List<DBAgent> agents; final List<DBAgent> agents;
final Map<String, DBMCPServer> mcps; final Map<String, DBMCPServer> mcps;
final List<DBProduct> products;
UserGetAppConfigResp( UserGetAppConfigResp(
{required this.env, required this.agents, required this.mcps}); {required this.env,
required this.agents,
required this.mcps,
required this.products});
factory UserGetAppConfigResp.fromJson(Map<String, dynamic> json) => factory UserGetAppConfigResp.fromJson(Map<String, dynamic> json) =>
_$UserGetAppConfigRespFromJson(json); _$UserGetAppConfigRespFromJson(json);
@ -63,3 +68,20 @@ class DBMCPServer {
_$DBMCPServerFromJson(json); _$DBMCPServerFromJson(json);
Map<String, dynamic> toJson() => _$DBMCPServerToJson(this); Map<String, dynamic> toJson() => _$DBMCPServerToJson(this);
} }
@JsonSerializable()
class DBProduct {
final String id;
final int factoryid;
final String devicename;
DBProduct({
required this.id,
required this.factoryid,
required this.devicename,
});
factory DBProduct.fromJson(Map<String, dynamic> json) =>
_$DBProductFromJson(json);
Map<String, dynamic> toJson() => _$DBProductToJson(this);
}

16
lib/data/models/appconfig_model.g.dart

@ -16,6 +16,9 @@ UserGetAppConfigResp _$UserGetAppConfigRespFromJson(
mcps: (json['mcps'] as Map<String, dynamic>).map( mcps: (json['mcps'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, DBMCPServer.fromJson(e as Map<String, dynamic>)), (k, e) => MapEntry(k, DBMCPServer.fromJson(e as Map<String, dynamic>)),
), ),
products: (json['products'] as List<dynamic>)
.map((e) => DBProduct.fromJson(e as Map<String, dynamic>))
.toList(),
); );
Map<String, dynamic> _$UserGetAppConfigRespToJson( Map<String, dynamic> _$UserGetAppConfigRespToJson(
@ -24,6 +27,7 @@ Map<String, dynamic> _$UserGetAppConfigRespToJson(
'env': instance.env, 'env': instance.env,
'agents': instance.agents, 'agents': instance.agents,
'mcps': instance.mcps, 'mcps': instance.mcps,
'products': instance.products,
}; };
DBAgent _$DBAgentFromJson(Map<String, dynamic> json) => DBAgent( DBAgent _$DBAgentFromJson(Map<String, dynamic> json) => DBAgent(
@ -66,3 +70,15 @@ Map<String, dynamic> _$DBMCPServerToJson(DBMCPServer instance) =>
'url': instance.url, 'url': instance.url,
'tools': instance.tools, 'tools': instance.tools,
}; };
DBProduct _$DBProductFromJson(Map<String, dynamic> json) => DBProduct(
id: json['id'] as String,
factoryid: (json['factoryid'] as num).toInt(),
devicename: json['devicename'] as String,
);
Map<String, dynamic> _$DBProductToJson(DBProduct instance) => <String, dynamic>{
'id': instance.id,
'factoryid': instance.factoryid,
'devicename': instance.devicename,
};

5
lib/data/models/user_Info.dart

@ -23,6 +23,7 @@ class User {
int viplv = 0; //添加会员等级字段,0表示普通用户 int viplv = 0; //添加会员等级字段,0表示普通用户
String earphonename; String earphonename;
String earphonemac; String earphonemac;
String earphonelicense;
String earphoneauthcode; String earphoneauthcode;
// final Int64 lastbettime; // final Int64 lastbettime;
// final Int64 createtime; // final Int64 createtime;
@ -44,6 +45,7 @@ class User {
required this.viplv, // 添加默认值 required this.viplv, // 添加默认值
required this.earphonename, required this.earphonename,
required this.earphonemac, required this.earphonemac,
required this.earphonelicense,
required this.earphoneauthcode, required this.earphoneauthcode,
// required this.lastbettime, // required this.lastbettime,
// required this.createtime, // required this.createtime,
@ -74,6 +76,7 @@ class User {
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonename: (userData?['earphonename'] as String?) ?? '', earphonename: (userData?['earphonename'] as String?) ?? '',
earphonemac: (userData?['earphonemac'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphonelicense: (userData?['earphonelicense'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
@ -102,6 +105,7 @@ class User {
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonename: (userData?['earphonename'] as String?) ?? '', earphonename: (userData?['earphonename'] as String?) ?? '',
earphonemac: (userData?['earphonemac'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphonelicense: (userData?['earphonelicense'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
@ -128,6 +132,7 @@ class User {
'earphonename': earphonename, 'earphonename': earphonename,
'earphonemac': earphonemac, 'earphonemac': earphonemac,
"earphonelicense": earphonelicense,
'earphoneauthcode': earphoneauthcode, 'earphoneauthcode': earphoneauthcode,
// 'lastbettime': lastbettime, // 'lastbettime': lastbettime,

4
lib/data/services/navigation_manager.dart

@ -2,11 +2,11 @@ import 'package:amap_service/amap_service.dart';
import 'package:deep_voice/core/utils/logger.dart'; import 'package:deep_voice/core/utils/logger.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
//音乐播放管理 //导航播放管理
class NavigationManager extends GetxService { class NavigationManager extends GetxService {
static NavigationManager get to => Get.find<NavigationManager>(); static NavigationManager get to => Get.find<NavigationManager>();
// 音乐服务实例 // 导航服务实例
final AmapService _amapervice = AmapService(); final AmapService _amapervice = AmapService();
/// 初始化 /// 初始化

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

@ -74,10 +74,19 @@ class Api {
} }
//app获取设备信息 //app获取设备信息
static getequipmentinfo([params]) { static getproduct([params]) {
return DioManager().request( return DioManager().request(
NWMethod.post, NWMethod.post,
'/api/home/user_getequipmentinfo', '/api/home/user_getproduct',
params: params,
);
}
//app获取设备信息
static getproducts([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_getproducts',
params: params, params: params,
); );
} }

4
lib/data/services/network/nw_api.dart

@ -1,5 +1,5 @@
class NWApi { class NWApi {
// static const baseApi = "https://www.deapsound.com"; // static const baseApi = "https://www.deapsound.com";
//static const baseApi = "https://api.deapsound.com"; static const baseApi = "https://api.deapsound.com";
static const baseApi = "https://api.ideapsound.com"; // static const baseApi = "https://api.ideapsound.com";
} }

27
lib/modules/agent/views/music_card.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
import '../controllers/agent_controller.dart'; import '../controllers/agent_controller.dart';
@ -20,10 +21,10 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
controller.musicStatus(); //刷新状态 controller.musicStatus(); //刷新状态
return Container( return Container(
padding: const EdgeInsets.all(14), padding: EdgeInsets.all(14.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: isDarkMode ? 0.2 : 0.03), color: Colors.black.withValues(alpha: isDarkMode ? 0.2 : 0.03),
@ -36,11 +37,11 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 50, width: 50.w,
height: 50, height: 50.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: isDarkMode ? Colors.grey[800] : Colors.grey[100],
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8.r),
image: image.isNotEmpty image: image.isNotEmpty
? DecorationImage( ? DecorationImage(
image: NetworkImage(image), image: NetworkImage(image),
@ -51,12 +52,12 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
child: image.isEmpty child: image.isEmpty
? Icon( ? Icon(
Icons.music_note, Icons.music_note,
size: 18, size: 18.sp,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
) )
: null, : null,
), ),
const SizedBox(width: 12), SizedBox(width: 12.w),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -64,18 +65,18 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
Text( Text(
name, name,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.grey[800], color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.grey[800],
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SizedBox(height: 2), SizedBox(height: 2.h),
Text( Text(
singer, singer,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

436
lib/modules/agent/views/navigation_card.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/agent_controller.dart'; import '../controllers/agent_controller.dart';
@ -30,7 +31,15 @@ String formatDuration(String secondsStr) {
return parts.join(''); return parts.join('');
} }
//导航卡片 /// 导航卡片组件
///
/// 各元素含义分析:
/// - [start]: 起点坐标 (经纬度格式)
/// - [end]: 终点坐标 (经纬度格式)
/// - [destination]: 目的地名称显示
/// - [modes]: 导航方式列表,包含驾车、步行、公交等选项
/// - [navigation]: 当前导航状态信息(可选)
/// - [height]: 卡片高度
Widget navigationCard( Widget navigationCard(
BuildContext context, { BuildContext context, {
required String start, required String start,
@ -38,70 +47,168 @@ Widget navigationCard(
required String destination, required String destination,
required List<Map<String, dynamic>> modes, required List<Map<String, dynamic>> modes,
Map<String, dynamic>? navigation, Map<String, dynamic>? navigation,
double height = 300, double? height, // 改为可选参数,允许自适应高度
}) { }) {
int currentIndex = 0; int currentIndex = 0;
final controller = Get.find<AgentController>(); // 获取控制器 final controller = Get.find<AgentController>();
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Container( return Container(
height: height, height: height?.h ?? 350.h, // 如果没有指定高度,使用350作为默认高度
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16.r),
color: Colors.white, color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white,
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 12,
color: Colors.black12, color: Colors.black.withValues(alpha: isDarkMode ? 0.4 : 0.08),
offset: Offset(0, 4), offset: const Offset(0, 2),
), ),
], ],
), ),
child: StatefulBuilder( child: StatefulBuilder(
builder: (context, setInnerState) { builder: (context, setInnerState) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 路线图片 // 顶部:路线预览图片区域
ClipRRect( Expanded(
borderRadius: flex: 4, // 增加图片区域的flex值,让图片占更多空间
const BorderRadius.vertical(top: Radius.circular(16)), child: ClipRRect(
child: AnimatedSwitcher( borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
duration: const Duration(milliseconds: 300), child: Stack(
child: Image.network( children: [
modes[currentIndex]["image"] as String, // 路线图片
key: ValueKey<String>(modes[currentIndex]["ntype"] as String), AnimatedSwitcher(
height: 160, duration: const Duration(milliseconds: 300),
width: double.infinity, child: Image.network(
fit: BoxFit.cover, modes[currentIndex]["image"] as String,
key: ValueKey<String>(
modes[currentIndex]["ntype"] as String),
height: double.infinity,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
child: Icon(
Icons.map_outlined,
size: 48.sp,
color: isDarkMode
? Colors.grey[600]
: Colors.grey[400],
),
);
},
),
),
// 导航状态覆盖层
if (navigation != null)
Positioned(
top: 12.h,
left: 12.w,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color:
Theme.of(context).primaryColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.navigation,
color: Colors.white,
size: 14.sp,
),
SizedBox(width: 4.w),
Text(
'导航中',
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
), ),
), ),
), ),
// 导航方式切换按钮 // 中部:导航方式选择器
Padding( Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(modes.length, (index) { children: List.generate(modes.length, (index) {
final mode = modes[index];
final isSelected = index == currentIndex; final isSelected = index == currentIndex;
return GestureDetector( final isEnabled = mode['enable'] as bool;
onTap: modes[index]['enable'] as bool
? () { return Expanded(
setInnerState(() => currentIndex = index); child: GestureDetector(
} onTap: isEnabled
: null, ? () {
child: SizedBox( setInnerState(() => currentIndex = index);
width: 50, }
child: Text( : null,
(modes[index]["ntype"] as String).tr, child: Container(
textAlign: TextAlign.center, margin: EdgeInsets.symmetric(horizontal: 4.w),
style: TextStyle( padding: EdgeInsets.symmetric(
color: modes[index]['enable'] vertical: 8.h, horizontal: 12.w),
? isSelected decoration: BoxDecoration(
? Colors.blue color: isSelected
: Colors.black87 ? Theme.of(context).primaryColor.withOpacity(0.1)
: Colors.grey, : Colors.transparent,
fontSize: 12, borderRadius: BorderRadius.circular(8.r),
fontWeight: FontWeight.w500, border: Border.all(
color: isSelected
? Theme.of(context).primaryColor
: (isDarkMode
? Colors.grey[700]!
: Colors.grey[300]!),
width: isSelected ? 2 : 1,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_getNavigationIcon(mode["ntype"] as String),
color: isEnabled
? (isSelected
? Theme.of(context).primaryColor
: (isDarkMode
? Colors.white70
: Colors.black87))
: Colors.grey,
size: 20.sp,
),
SizedBox(height: 4.h),
Text(
(mode["ntype"] as String).tr,
textAlign: TextAlign.center,
style: TextStyle(
color: isEnabled
? (isSelected
? Theme.of(context).primaryColor
: (isDarkMode
? Colors.white70
: Colors.black87))
: Colors.grey,
fontSize: 11.sp,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.w400,
),
),
],
), ),
), ),
), ),
@ -110,42 +217,191 @@ Widget navigationCard(
), ),
), ),
// 距离和耗时 // 底部:目的地信息和操作按钮
Padding( Container(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h),
child: Row( child: Stack(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( // 目的地和距离时间信息
crossAxisAlignment: CrossAxisAlignment.start, Padding(
children: [ padding: EdgeInsets.only(right: 70.w), // 为右下角按钮留出空间
Text('$destination', child: Column(
style: const TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 12, color: Colors.black)), children: [
Text( // 路径显示:当前位置 → 目的地
'${formatDistance(modes[currentIndex]["distance"])}-约${formatDuration(modes[currentIndex]["duration"])}', Row(
style: const TextStyle( children: [
fontSize: 15, color: Colors.blue)), // 起点图标
if (navigation != null) Icon(
Text( Icons.my_location,
"导航中... ${navigation["distanceText"]}进入${navigation["nextRoad"]}", size: 16.sp,
style: TextStyle( color: Theme.of(context).primaryColor,
fontSize: 12, ),
color: SizedBox(width: 6.w),
const Color.fromARGB(255, 164, 164, 164))) // 当前位置文本
], Text(
'当前位置',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
SizedBox(width: 8.w),
// 箭头图标
Icon(
Icons.arrow_forward,
size: 16.sp,
color: isDarkMode
? Colors.white54
: Colors.black45,
),
SizedBox(width: 8.w),
// 目的地图标
Icon(
Icons.location_on,
size: 16.sp,
color: Colors.red,
),
SizedBox(width: 6.w),
// 目的地名称
Expanded(
child: Text(
destination,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode
? Colors.white
: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
SizedBox(height: 4.h),
// 距离和时间信息
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 距离信息
Row(
children: [
Icon(
Icons.straighten,
size: 14.sp,
color: Theme.of(context).primaryColor,
),
SizedBox(width: 4.w),
Text(
formatDistance(
modes[currentIndex]["distance"]),
style: TextStyle(
fontSize: 14.sp,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500,
),
),
],
),
SizedBox(height: 4.h),
// 时间信息
Row(
children: [
Icon(
Icons.access_time,
size: 14.sp,
color: Theme.of(context).primaryColor,
),
SizedBox(width: 4.w),
Text(
'约${formatDuration(modes[currentIndex]["duration"])}',
style: TextStyle(
fontSize: 14.sp,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
// 导航状态信息
if (navigation != null) ...[
SizedBox(height: 6.h),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Theme.of(context)
.primaryColor
.withOpacity(0.1),
borderRadius: BorderRadius.circular(6.r),
),
child: Text(
"${navigation["distanceText"]}进入${navigation["nextRoad"]}",
style: TextStyle(
fontSize: 12.sp,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500,
),
),
),
],
],
),
), ),
CircleAvatar( // 开始导航按钮 - 绝对定位到右下角
backgroundColor: Colors.blue, Positioned(
radius: 18, right: 0,
child: IconButton( bottom: 0,
icon: const Icon(Icons.turn_left, child: Container(
color: Colors.white, size: 20), decoration: BoxDecoration(
onPressed: () { gradient: LinearGradient(
controller.openNavigation(start, end, ""); colors: [
}, Theme.of(context).primaryColor,
Theme.of(context).primaryColor.withOpacity(0.8),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Theme.of(context)
.primaryColor
.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12.r),
onTap: () {
controller.openNavigation(start, end, "");
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
child: Icon(
Icons.navigation,
color: Colors.white,
size: 20.sp,
),
),
),
),
), ),
) ),
], ],
), ),
), ),
@ -155,3 +411,25 @@ Widget navigationCard(
), ),
); );
} }
/// 根据导航类型获取对应图标
IconData _getNavigationIcon(String ntype) {
switch (ntype.toLowerCase()) {
case 'driving':
case '驾车':
return Icons.directions_car;
case 'walking':
case '步行':
return Icons.directions_walk;
case 'transit':
case 'tsransit': // 修正拼写错误
case '公交':
return Icons.directions_transit;
case 'bicycling': // 修正为正确的英文值
case 'cycling':
case '骑行':
return Icons.directions_bike;
default:
return Icons.navigation;
}
}

110
lib/modules/agent/views/travel_card.dart

@ -1,29 +1,32 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// 构建旅游内容卡片 /// 构建旅游内容卡片
Widget buildTravelContent( Widget buildTravelContent(
BuildContext context, List<Map<String, dynamic>> travellist) { BuildContext context, List<Map<String, dynamic>> travellist) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'以下是一些推搜索结果:', '以下是一些推荐搜索结果:',
style: TextStyle( style: TextStyle(
color: Colors.black87, color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87,
fontSize: 14, fontSize: 14.sp,
height: 1.4, height: 1.4,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
const SizedBox(height: 12), SizedBox(height: 12.h),
Container( Container(
padding: const EdgeInsets.all(12), padding: EdgeInsets.all(12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.03), color: Colors.black.withValues(alpha: isDarkMode ? 0.2 : 0.03),
blurRadius: 3, blurRadius: 3,
spreadRadius: 1, spreadRadius: 1,
offset: const Offset(0, 1), offset: const Offset(0, 1),
@ -53,16 +56,16 @@ Widget buildTravelContent(
// ), // ),
// const SizedBox(height: 10), // const SizedBox(height: 10),
// 动态生成音乐条目 // 动态生成旅游景点条目
...travellist.asMap().entries.map((entry) { ...travellist.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final item = entry.value; final item = entry.value;
final name = item["name"] ?? "未知歌曲"; //名字 final name = item["name"] ?? "未知景点"; //名字
final addr = item["addr"] ?? ""; //地址 final addr = item["addr"] ?? ""; //地址
final tag = item["tag"] ?? "未知歌手"; //标签 // final tag = item["tag"] ?? "未知标签"; //标签
final rating = item["rating"] ?? ""; //评分 // final rating = item["rating"] ?? ""; //评分
final distance = item["distance"] ?? ""; //距离 // final distance = item["distance"] ?? ""; //距离
final images = item["images"] ?? []; // 图片列表 // final images = item["images"] ?? []; // 图片列表
return Column( return Column(
children: [ children: [
buildLocationItem( buildLocationItem(
@ -71,7 +74,11 @@ Widget buildTravelContent(
addr, addr,
), ),
if (index != travellist.length - 1) if (index != travellist.length - 1)
Divider(height: 1, thickness: 1, color: Colors.grey[100]), Divider(
height: 1.h,
thickness: 1,
color: isDarkMode ? Colors.grey[700] : Colors.grey[100]
),
], ],
); );
}).toList(), }).toList(),
@ -82,19 +89,19 @@ Widget buildTravelContent(
// Divider(height: 1, thickness: 1, color: Colors.grey[100]), // Divider(height: 1, thickness: 1, color: Colors.grey[100]),
// _buildLocationItem('浅草寺', '东京最古老的寺庙', context), // _buildLocationItem('浅草寺', '东京最古老的寺庙', context),
const SizedBox(height: 8), SizedBox(height: 8.h),
Center( Center(
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: () {},
style: TextButton.styleFrom( style: TextButton.styleFrom(
minimumSize: Size.zero, minimumSize: Size.zero,
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
tapTargetSize: MaterialTapTargetSize.shrinkWrap, tapTargetSize: MaterialTapTargetSize.shrinkWrap,
), ),
child: Text( child: Text(
'查看更多景点', '查看更多景点',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@ -104,41 +111,41 @@ Widget buildTravelContent(
], ],
), ),
), ),
const SizedBox(height: 10), SizedBox(height: 10.h),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
child: OutlinedButton.icon( child: OutlinedButton.icon(
onPressed: () {}, onPressed: () {},
icon: Icon(Icons.map, size: 16), icon: Icon(Icons.map, size: 16.sp),
label: Text('查看地图'), label: const Text('查看地图'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).primaryColor, foregroundColor: Theme.of(context).primaryColor,
side: BorderSide( side: BorderSide(
color: Theme.of(context).primaryColor.withOpacity(0.5)), color: Theme.of(context).primaryColor.withValues(alpha: 0.5)),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16.r),
), ),
textStyle: TextStyle(fontSize: 12), textStyle: TextStyle(fontSize: 12.sp),
), ),
), ),
), ),
SizedBox(width: 8), SizedBox(width: 8.w),
Expanded( Expanded(
child: OutlinedButton.icon( child: OutlinedButton.icon(
onPressed: () {}, onPressed: () {},
icon: Icon(Icons.bookmark_border, size: 16), icon: Icon(Icons.bookmark_border, size: 16.sp),
label: Text('保存到收藏'), label: const Text('保存到收藏'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey[700], foregroundColor: isDarkMode ? Colors.grey[400] : Colors.grey[700],
side: BorderSide(color: Colors.grey[400]!), side: BorderSide(color: isDarkMode ? Colors.grey[600]! : Colors.grey[400]!),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16.r),
), ),
textStyle: TextStyle(fontSize: 12), textStyle: TextStyle(fontSize: 12.sp),
), ),
), ),
), ),
@ -154,23 +161,25 @@ Widget buildLocationItem(
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 38, width: 38.w,
height: 38, height: 38.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? Color(0xFF1A365D) : Colors.blue[50], color: isDarkMode
borderRadius: BorderRadius.circular(8), ? const Color(0xFF2A2A2A)
: Theme.of(context).primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8.r),
), ),
child: Icon( child: Icon(
Icons.place, Icons.place,
size: 18, size: 18.sp,
color: Color(0xFFE11D48), color: Theme.of(context).primaryColor,
), ),
), ),
const SizedBox(width: 12), SizedBox(width: 12.w),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -178,19 +187,19 @@ Widget buildLocationItem(
Text( Text(
name, name,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.9) ? Colors.white.withValues(alpha: 0.9)
: Colors.grey[800], : Colors.grey[800],
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SizedBox(height: 2), SizedBox(height: 2.h),
Text( Text(
description, description,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -200,10 +209,13 @@ Widget buildLocationItem(
), ),
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: Icon(Icons.arrow_forward, color: Colors.grey[400]), icon: Icon(
Icons.arrow_forward,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400]
),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: BoxConstraints(), constraints: const BoxConstraints(),
iconSize: 20, iconSize: 20.sp,
), ),
], ],
), ),

213
lib/modules/agent/views/weather_card.dart

@ -1,5 +1,6 @@
import 'package:common_utils/common_utils.dart'; import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// 构建天气内容卡片 /// 构建天气内容卡片
Widget buildWeatherContent(BuildContext context, String city, Widget buildWeatherContent(BuildContext context, String city,
@ -12,31 +13,31 @@ Widget buildWeatherContent(BuildContext context, String city,
Text( Text(
'查询天气情况:', '查询天气情况:',
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87,
fontSize: 14, fontSize: 14.sp,
height: 1.4, height: 1.4,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
const SizedBox(height: 12), SizedBox(height: 12.h),
Container( Container(
padding: const EdgeInsets.all(16), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: isDarkMode gradient: isDarkMode
? LinearGradient( ? const LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)], colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)],
) )
: LinearGradient( : const LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)], colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.1),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -64,37 +65,37 @@ Widget buildWeatherContent(BuildContext context, String city,
Text( Text(
now["dayweather"], now["dayweather"],
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14.sp,
color: Colors.white.withOpacity(0.9), color: Colors.white.withValues(alpha: 0.9),
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8.h),
Row( Row(
children: [ children: [
Text( Text(
"${now["daytemp"]}°", "${now["daytemp"]}°",
style: const TextStyle( style: TextStyle(
fontSize: 36, fontSize: 36.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,
), ),
), ),
SizedBox(width: 8), SizedBox(width: 8.w),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'最高: ${now["daytemp"]}°', '最高: ${now["daytemp"]}°',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: Colors.white.withOpacity(0.9), color: Colors.white.withValues(alpha: 0.9),
), ),
), ),
Text( Text(
'最低: ${now["nighttemp"]}°', '最低: ${now["nighttemp"]}°',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: Colors.white.withOpacity(0.9), color: Colors.white.withValues(alpha: 0.9),
), ),
), ),
], ],
@ -105,26 +106,26 @@ Widget buildWeatherContent(BuildContext context, String city,
), ),
Icon( Icon(
getWeatherIcon(now['dayweather']), getWeatherIcon(now['dayweather']),
size: 48, size: 48.sp,
color: Colors.white, color: Colors.white,
), ),
], ],
), ),
SizedBox(height: 16), SizedBox(height: 16.h),
Container( Container(
height: 1, height: 1,
color: Colors.white.withOpacity(isDarkMode ? 0.15 : 0.2), color: Colors.white.withValues(alpha: isDarkMode ? 0.15 : 0.2),
), ),
SizedBox(height: 16), SizedBox(height: 16.h),
const Text( Text(
'未来预报', '未来预报',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white, color: Colors.white,
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8.h),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate( children: List.generate(
@ -145,35 +146,8 @@ Widget buildWeatherContent(BuildContext context, String city,
}, },
), ),
), ),
SizedBox(height: 16), SizedBox(height: 16.h),
Container( _buildWeatherTip(context, now, isDarkMode),
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.15)
: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(
Icons.info_outline,
size: 16,
color: Colors.white,
),
SizedBox(width: 8),
Flexible(
child: Text(
'今日紫外线指数较高,外出请做好防晒',
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
),
),
], ],
), ),
), ),
@ -181,6 +155,125 @@ Widget buildWeatherContent(BuildContext context, String city,
); );
} }
/// 构建智能天气提示
Widget _buildWeatherTip(BuildContext context, Map<String, dynamic> now, bool isDarkMode) {
final weatherTip = _generateWeatherTip(now);
return Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 12.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withValues(alpha: 0.15)
: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
children: [
Icon(
weatherTip.icon,
size: 16.sp,
color: Colors.white,
),
SizedBox(width: 8.w),
Flexible(
child: Text(
weatherTip.message,
style: TextStyle(
fontSize: 12.sp,
color: Colors.white,
),
),
),
],
),
);
}
/// 天气提示数据类
class WeatherTip {
final IconData icon;
final String message;
const WeatherTip({required this.icon, required this.message});
}
/// 根据天气数据生成智能提示
WeatherTip _generateWeatherTip(Map<String, dynamic> now) {
final dayWeather = (now['dayweather'] as String? ?? '').trim();
final dayTemp = int.tryParse(now['daytemp']?.toString() ?? '0') ?? 0;
final nightTemp = int.tryParse(now['nighttemp']?.toString() ?? '0') ?? 0;
// 高温提示 (>= 35°C)
if (dayTemp >= 35) {
return const WeatherTip(
icon: Icons.local_fire_department,
message: '今日高温炎热,请注意防暑降温,多喝水避免中暑',
);
}
// 低温提示 (<= 5°C)
if (dayTemp <= 5) {
return const WeatherTip(
icon: Icons.ac_unit,
message: '今日气温较低,外出请注意保暖,小心感冒',
);
}
// 雨天提示
if (dayWeather.contains('雨') || ['阵雨', '雷阵雨', '小雨', '中雨', '大雨', '暴雨'].any((rain) => dayWeather.contains(rain))) {
return const WeatherTip(
icon: Icons.umbrella,
message: '今日有雨,外出请携带雨具,注意出行安全',
);
}
// 雪天提示
if (dayWeather.contains('雪') || ['阵雪', '小雪', '中雪', '大雪', '暴雪'].any((snow) => dayWeather.contains(snow))) {
return const WeatherTip(
icon: Icons.ac_unit,
message: '今日有雪,路面湿滑请注意安全,做好保暖措施',
);
}
// 雾霾提示
if (dayWeather.contains('雾') || dayWeather.contains('霾') || ['雾', '浓雾', '霾', '中度霾', '重度霾'].any((fog) => dayWeather.contains(fog))) {
return const WeatherTip(
icon: Icons.blur_on,
message: '今日有雾霾,能见度较低,外出请佩戴口罩',
);
}
// 大风提示
if (dayWeather.contains('风') || ['大风', '强风', '疾风', '烈风'].any((wind) => dayWeather.contains(wind))) {
return const WeatherTip(
icon: Icons.air,
message: '今日风力较大,外出请注意安全,固定好物品',
);
}
// 晴天紫外线提示 (温度 >= 25°C)
if (['晴', '少云', '晴间多云'].any((sunny) => dayWeather.contains(sunny)) && dayTemp >= 25) {
return const WeatherTip(
icon: Icons.wb_sunny,
message: '今日阳光充足紫外线较强,外出请做好防晒',
);
}
// 温差大提示 (>= 10°C)
if ((dayTemp - nightTemp).abs() >= 10) {
return const WeatherTip(
icon: Icons.thermostat,
message: '今日昼夜温差较大,请适时增减衣物',
);
}
// 默认舒适天气提示
return const WeatherTip(
icon: Icons.wb_sunny_outlined,
message: '今日天气适宜,是外出活动的好时光',
);
}
/// 构建单日天气预报 /// 构建单日天气预报
Widget _buildForecastDay(String day, IconData icon, String temp) { Widget _buildForecastDay(String day, IconData icon, String temp) {
return Column( return Column(
@ -188,21 +281,21 @@ Widget _buildForecastDay(String day, IconData icon, String temp) {
Text( Text(
day, day,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
color: Colors.white.withOpacity(0.9), color: Colors.white.withValues(alpha: 0.9),
), ),
), ),
SizedBox(height: 4), SizedBox(height: 4.h),
Icon( Icon(
icon, icon,
size: 20, size: 20.sp,
color: Colors.white, color: Colors.white,
), ),
SizedBox(height: 4), SizedBox(height: 4.h),
Text( Text(
temp, temp,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.white, color: Colors.white,
), ),

14
lib/modules/devices/controllers/devices_controller.dart

@ -13,12 +13,14 @@ import 'package:get/get_state_manager/src/simple/get_controllers.dart';
class DevicesController extends GetxController { class DevicesController extends GetxController {
late final BleManager _bleManager; late final BleManager _bleManager;
//耳机绑定 //耳机绑定
final isearphone = false.obs; final earphonestate = 0.obs;
final earphoneName = "".obs; final earphoneName = "".obs;
final earphoneMac = "".obs; final earphoneMac = "".obs;
final earphoneLicense = "".obs;
final earphoneCode = "".obs; final earphoneCode = "".obs;
//录音笔 //录音笔
final voicepenstate = 0.obs;
final isvoicepen = false.obs; final isvoicepen = false.obs;
final voicepenName = "".obs; final voicepenName = "".obs;
final voicepenMac = "".obs; final voicepenMac = "".obs;
@ -29,9 +31,13 @@ class DevicesController extends GetxController {
_bleManager = Get.find<BleManager>(); _bleManager = Get.find<BleManager>();
earphoneName.value = User.instance.earphonename; earphoneName.value = User.instance.earphonename;
earphoneMac.value = User.instance.earphonemac; earphoneMac.value = User.instance.earphonemac;
earphoneLicense.value = User.instance.earphonelicense;
earphoneCode.value = User.instance.earphoneauthcode; earphoneCode.value = User.instance.earphoneauthcode;
if (earphoneCode.value != "") { if (earphoneCode.value != "") {
isearphone.value = true; earphonestate.value = 1;
}
if (earphoneLicense.value != "") {
earphonestate.value = 2;
} }
} }
@ -48,8 +54,8 @@ class DevicesController extends GetxController {
if (response != null) { if (response != null) {
User.instance.earphonename = ""; User.instance.earphonename = "";
User.instance.earphonemac = ""; User.instance.earphonemac = "";
User.instance.earphoneauthcode = ""; User.instance.earphonelicense = "";
isearphone.value = false; earphonestate.value = 0;
_bleManager.clearPairedDevices(); _bleManager.clearPairedDevices();
_bleManager.disconnect(); _bleManager.disconnect();
Get.toNamed(Routes.pairing); Get.toNamed(Routes.pairing);

6
lib/modules/devices/views/devices_view.dart

@ -93,10 +93,10 @@ class DevicesView extends GetView<DevicesController> {
() => () =>
// 耳机列表 // 耳机列表
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: controller.isearphone.value title: controller.earphonestate.value != 0
? controller.earphoneName.value ? controller.earphoneName.value
: "未绑定", : "未绑定",
subtitle: controller.isearphone.value subtitle: controller.earphonestate.value != 0
? controller.earphoneMac.value ? controller.earphoneMac.value
: "还未绑定设备", : "还未绑定设备",
icon: Icons.headphones, icon: Icons.headphones,
@ -110,7 +110,7 @@ class DevicesView extends GetView<DevicesController> {
titleColor: isDarkMode ? Colors.white : null, titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null, subtitleColor: isDarkMode ? Colors.white70 : null,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
showbutt: controller.isearphone.value, showbutt: controller.earphonestate == 2,
onPressed: () { onPressed: () {
controller.unbindearphone(); controller.unbindearphone();
}, },

6
lib/modules/home/controllers/home_controller.dart

@ -129,7 +129,7 @@ class HomeController extends GetxController {
update(); update();
} catch (e) { } catch (e) {
Logger.e(_tag, '检查连接状态失败: $e'); Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e');
if (_isFullyConnected.value) { if (_isFullyConnected.value) {
_isFullyConnected.value = false; _isFullyConnected.value = false;
_isFullyConnected.refresh(); _isFullyConnected.refresh();
@ -217,7 +217,7 @@ class HomeController extends GetxController {
rightStatus = rightStatus =
deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
return '左耳: $leftStatus / 右耳: $rightStatus'; return '${'leftEar'.tr}: $leftStatus / ${'rightEar'.tr}: $rightStatus'; // 左耳: xxx / 右耳: xxx
} }
String getCaseText() { String getCaseText() {
@ -232,7 +232,7 @@ class HomeController extends GetxController {
caseStatus = '$caseStatus ⚡'; caseStatus = '$caseStatus ⚡';
} }
return '充电仓: $caseStatus'; return '${'chargingCase'.tr}: $caseStatus'; // 充电仓: xxx
} }
String getChargingText() { String getChargingText() {

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

@ -109,7 +109,7 @@ class HomeView extends GetView<HomeController> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'DeepSound', 'DeapSound',
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -395,7 +395,7 @@ class HomeView extends GetView<HomeController> {
children: [ children: [
// 左耳机 // 左耳机
_buildCompactBatteryItem( _buildCompactBatteryItem(
label: '左', label: 'left'.tr, // 左
batteryLevel: leftBattery.value, batteryLevel: leftBattery.value,
isCharging: leftEarCharging.value, isCharging: leftEarCharging.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
@ -410,7 +410,7 @@ class HomeView extends GetView<HomeController> {
// 右耳机 // 右耳机
_buildCompactBatteryItem( _buildCompactBatteryItem(
label: '右', label: 'right'.tr, // 右
batteryLevel: rightBattery.value, batteryLevel: rightBattery.value,
isCharging: rightEarCharging.value, isCharging: rightEarCharging.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,

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

@ -11,7 +11,6 @@ import 'weixinLogin.dart';
import '../../../data/services/network/api.dart'; import '../../../data/services/network/api.dart';
import '../../../../data/models/user_Info.dart'; import '../../../../data/models/user_Info.dart';
import '../../common/webview_page.dart'; import '../../common/webview_page.dart';
//import 'package:shared_preferences/shared_preferences.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'dart:io'; import 'dart:io';
@ -914,10 +913,6 @@ class LoginController extends GetxController {
print('getAppConfig:进来了'); print('getAppConfig:进来了');
// 根据API设计调整 // 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response); final resp = UserGetAppConfigResp.fromJson(response);
// 存储用户信息(示例)
// _storage.write('ENV', resp.env);
// _storage.write('AGENTS', resp.agents);
// _storage.write('MCPS', resp.mcps);
AppConfig.initialize(resp); AppConfig.initialize(resp);
return true; return true;
} catch (e) { } catch (e) {

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

@ -236,8 +236,8 @@ class LoginView extends GetView<LoginController> {
controller: controller.contactController, controller: controller.contactController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: controller.selectedCountryFlag == '✉️' hintText: controller.selectedCountryFlag == '✉️'
? 'email'.tr // 需要在翻译文件中添加 ? 'email'.tr // 邮箱
: 'phoneNumber'.tr, // 需要在翻译文件中添加 : 'phoneNumber'.tr, // 手机号码
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: isDarkMode color: isDarkMode
@ -303,7 +303,7 @@ class LoginView extends GetView<LoginController> {
style: TextStyle(fontSize: 24.sp), style: TextStyle(fontSize: 24.sp),
), ),
title: Text( title: Text(
'${country['name']}'.tr, '${country['name']}'.tr, // 国家名称
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
@ -584,7 +584,7 @@ class LoginView extends GetView<LoginController> {
), ),
SizedBox(width: 4.w), SizedBox(width: 4.w),
Text( Text(
'expressConsent'.tr, 'expressConsent'.tr, // 登录/注册即表示你同意
style: TextStyle( style: TextStyle(
fontSize: 13.sp, fontSize: 13.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700], color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
@ -680,7 +680,7 @@ class LoginView extends GetView<LoginController> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'输入验证码', 'enterVerificationCode'.tr, // 输入验证码
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -689,7 +689,7 @@ class LoginView extends GetView<LoginController> {
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Obx(() => Text( Obx(() => Text(
'验证码已发送至 ${controller.displayContact}', '${'verificationCodeSentTo'.tr} ${controller.displayContact}', // 验证码已发送至
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
@ -767,8 +767,8 @@ class LoginView extends GetView<LoginController> {
), ),
label: Text( label: Text(
controller.countdownSeconds > 0 controller.countdownSeconds > 0
? '重新发送验证码 (${controller.countdownSeconds}s)' ? '${'resendVerificationCode'.tr} (${controller.countdownSeconds}s)' // 重新发送验证码 (倒计时)
: '重新发送验证码', : 'resendVerificationCode'.tr, // 重新发送验证码
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: controller.countdownSeconds > 0 color: controller.countdownSeconds > 0
@ -808,7 +808,7 @@ class LoginView extends GetView<LoginController> {
), ),
) )
: Text( : Text(
'登录', 'login'.tr, // 登录
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -824,7 +824,7 @@ class LoginView extends GetView<LoginController> {
child: TextButton( child: TextButton(
onPressed: controller.usePasswordLogin, onPressed: controller.usePasswordLogin,
child: Text( child: Text(
'使用密码登录', 'usePasswordLogin'.tr, // 使用密码登录
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: color:

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

@ -63,6 +63,10 @@ class OtaUpgradeController extends GetxController {
final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性 final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性
final RxInt type = OtaEvent.NOT_STARTED.obs; final RxInt type = OtaEvent.NOT_STARTED.obs;
final RxBool isUpgrade = false.obs; final RxBool isUpgrade = false.obs;
// 新增:详细状态提示
final RxString detailedStatus = ''.obs;
final RxBool showDetailedStatus = false.obs;
@override @override
void onInit() { void onInit() {
@ -184,7 +188,7 @@ class OtaUpgradeController extends GetxController {
jlota.init(); jlota.init();
// API请求 // API请求
final response = await Api.getequipmentinfo({ final response = await Api.getproduct({
"id": deviceInfo.firmwareProductId, "id": deviceInfo.firmwareProductId,
}); });
@ -271,6 +275,12 @@ class OtaUpgradeController extends GetxController {
Get.back(); Get.back();
Future.delayed(Duration(milliseconds: 300)); Future.delayed(Duration(milliseconds: 300));
// 检查设备电量
if (!_checkBatteryLevel()) {
return; // 电量不足,不执行升级
}
startDownload(); startDownload();
}, },
child: Text('更新'), child: Text('更新'),
@ -283,6 +293,9 @@ class OtaUpgradeController extends GetxController {
// 修改后的startDownload方法 // 修改后的startDownload方法
Future<void> startDownload() async { Future<void> startDownload() async {
isDownloading.value = true; isDownloading.value = true;
showDetailedStatus.value = true;
detailedStatus.value = '准备下载固件包...';
int lastIndex = updatepackageaddress.lastIndexOf('/'); int lastIndex = updatepackageaddress.lastIndexOf('/');
String fileName = updatepackageaddress.substring(lastIndex + 1); String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades"; String directoryName = "ota_upgrades";
@ -296,6 +309,7 @@ class OtaUpgradeController extends GetxController {
type.value = DOWNLOADING_UP_FILES; type.value = DOWNLOADING_UP_FILES;
try { try {
detailedStatus.value = '正在获取下载目录...';
final Directory? downloadsDir = await getApplicationSupportDirectory(); final Directory? downloadsDir = await getApplicationSupportDirectory();
if (downloadsDir == null) { if (downloadsDir == null) {
throw FileSystemException('无法访问下载目录'); throw FileSystemException('无法访问下载目录');
@ -304,12 +318,14 @@ class OtaUpgradeController extends GetxController {
final targetDir = final targetDir =
Directory('${downloadsDir.path}/Download/$directoryName'); Directory('${downloadsDir.path}/Download/$directoryName');
if (!await targetDir.exists()) { if (!await targetDir.exists()) {
detailedStatus.value = '创建下载目录...';
await targetDir.create(recursive: true); await targetDir.create(recursive: true);
} }
final savePath = '${targetDir.path}/$fileName'; final savePath = '${targetDir.path}/$fileName';
// 新增:检查同名文件逻辑 // 新增:检查同名文件逻辑
detailedStatus.value = '检查本地文件...';
bool existingSameFile = false; bool existingSameFile = false;
try { try {
var files = await targetDir.list().toList(); var files = await targetDir.list().toList();
@ -330,12 +346,13 @@ class OtaUpgradeController extends GetxController {
if (existingSameFile) { if (existingSameFile) {
//Get.snackbar('文件已存在', '跳过下载,直接更新'); //Get.snackbar('文件已存在', '跳过下载,直接更新');
detailedStatus.value = '文件已存在,准备升级...';
startUpgrading(savePath); startUpgrading(savePath);
return; return;
} }
// 新增:删除其他文件逻辑 // 新增:删除其他文件逻辑
detailedStatus.value = '清理旧文件...';
try { try {
var files = await targetDir.list().toList(); var files = await targetDir.list().toList();
for (var entity in files) { for (var entity in files) {
@ -351,6 +368,7 @@ class OtaUpgradeController extends GetxController {
print('文件保存路径: $savePath'); print('文件保存路径: $savePath');
detailedStatus.value = '开始下载固件包...';
final options = Options( final options = Options(
receiveTimeout: Duration.zero, receiveTimeout: Duration.zero,
followRedirects: true, followRedirects: true,
@ -364,14 +382,19 @@ class OtaUpgradeController extends GetxController {
onReceiveProgress: (received, total) { onReceiveProgress: (received, total) {
if (total != -1) { if (total != -1) {
downloadProgress.value = received / total; downloadProgress.value = received / total;
detailedStatus.value = '正在下载固件包... ${(received / total * 100).toStringAsFixed(1)}%';
} }
}, },
); );
File file = File(savePath); File file = File(savePath);
if (await file.exists()) { if (await file.exists()) {
// Get.snackbar('下载完成', '文件已保存至: $savePath'); // Get.snackbar('下载完成', '文件已保存至: $savePath');
detailedStatus.value = '下载完成,准备升级...';
} else { } else {
Get.snackbar('错误', '文件未找到,请检查写入权限'); Get.snackbar('错误', '文件未找到,请检查写入权限');
detailedStatus.value = '下载失败';
showDetailedStatus.value = false;
return;
} }
isDownloading.value = false; isDownloading.value = false;
startUpgrading(savePath); startUpgrading(savePath);
@ -381,12 +404,19 @@ class OtaUpgradeController extends GetxController {
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}'); Get.snackbar('存储失败', '无法写入文件: ${e.message}');
} catch (e) { } catch (e) {
Get.snackbar('未知错误', e.toString()); isDownloading.value = false;
detailedStatus.value = '下载失败,请检查网络连接';
showDetailedStatus.value = false;
Get.snackbar('下载失败', '请检查网络连接: $e');
print('下载失败: $e');
} }
} }
// 开始升级 // 开始升级
void startUpgrading(String path) async { void startUpgrading(String path) async {
detailedStatus.value = '准备设备连接...';
await Future.delayed(const Duration(milliseconds: 500));
detailedStatus.value = '正在连接设备...';
jlota.startUpgrading(address: _address, filePath: path); jlota.startUpgrading(address: _address, filePath: path);
return; return;
// isUpgrade.value = false; // isUpgrade.value = false;
@ -399,6 +429,60 @@ class OtaUpgradeController extends GetxController {
// isUpgrade.value = false; // isUpgrade.value = false;
} }
// 检查设备电量是否满足升级条件
bool _checkBatteryLevel() {
try {
final deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo == null) {
Get.snackbar(
'电量检查失败',
'无法获取设备信息,请检查设备连接',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange,
colorText: Colors.white,
);
return false;
}
final leftBattery = deviceInfo.leftEarBattery;
final rightBattery = deviceInfo.rightEarBattery;
// 检查左右耳电量是否都大于30%
if (leftBattery <= 30 || rightBattery <= 30) {
String message = '设备电量不足,无法进行固件升级\n';
message += '升级要求:左右耳电量均需大于30%\n';
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
message += '请充电后再试';
Get.dialog(
AlertDialog(
title: Text('电量不足'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('确定'),
),
],
),
);
return false;
}
return true;
} catch (e) {
Get.snackbar(
'电量检查失败',
'检查设备电量时发生错误:$e',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return false;
}
}
void _listenToEvents() { void _listenToEvents() {
Ota.instance.eventStream.listen((event) { Ota.instance.eventStream.listen((event) {
if (event.event == 'onProgress' && if (event.event == 'onProgress' &&
@ -407,29 +491,38 @@ class OtaUpgradeController extends GetxController {
progress.value = event.progress!; progress.value = event.progress!;
type.value = event.type!; type.value = event.type!;
isUpgrade.value = true; // 开始升级 isUpgrade.value = true; // 开始升级
detailedStatus.value = '升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%';
} }
if (event.event == 'onStartOTA') { if (event.event == 'onStartOTA') {
type.value = OtaEvent.START_UPGRADING; type.value = START_UPGRADING;
isUpgrade.value = true; // 开始升级 isUpgrade.value = true; // 开始升级
detailedStatus.value = 'OTA升级已开始...';
} }
if (event.event == 'onStopOTA') { if (event.event == 'onStopOTA') {
type.value = OtaEvent.UPGRADE_SUCCESS; type.value = UPGRADE_SUCCESS;
isUpgrade.value = false; isUpgrade.value = false;
detailedStatus.value = '升级成功完成';
showDetailedStatus.value = false;
Get.snackbar('升级成功', '请退出重新连接设备'); Get.snackbar('升级成功', '请退出重新连接设备');
_getCurrentVersion(); _getCurrentVersion();
} }
if (event.event == 'onError') { if (event.event == 'onError') {
type.value = OtaEvent.UPGRADE_FAILED; type.value = UPGRADE_FAILED;
isUpgrade.value = false; isUpgrade.value = false;
detailedStatus.value = '升级失败,错误码:${event.code}';
showDetailedStatus.value = false;
Get.snackbar('升级失败', '请检查设备连接或升级包,错误码:${event.code}'); Get.snackbar('升级失败', '请检查设备连接或升级包,错误码:${event.code}');
} }
if (event.event == 'NOCONNECTION') { if (event.event == 'NOCONNECTION') {
type.value = OtaEvent.CONNECTION_DISCONNECTION; type.value = CONNECTION_DISCONNECTION;
isUpgrade.value = false; isUpgrade.value = false;
detailedStatus.value = '设备连接断开';
showDetailedStatus.value = false;
} }
if (event.event == 'CONNECTION_SUCCESS') { if (event.event == 'CONNECTION_SUCCESS') {
type.value = OtaEvent.CONNECTION_SUCCESS; type.value = CONNECTION_SUCCESS;
isUpgrade.value = true; // 开始升级 isUpgrade.value = true; // 开始升级
detailedStatus.value = '设备连接成功';
} }
}); });
} }

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

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/ota_upgrade_controller.dart'; import '../controllers/ota_upgrade_controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/services/ble_manager.dart';
class OtaUpgradeView extends GetView<OtaUpgradeController> { class OtaUpgradeView extends GetView<OtaUpgradeController> {
const OtaUpgradeView({Key? key}) : super(key: key); const OtaUpgradeView({Key? key}) : super(key: key);
@ -89,17 +90,16 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// 设备信息卡片 - 优化后的版本 // 设备信息卡片 - 优化后的版本
_buildDeviceCard(isDarkMode), _buildDeviceCard(isDarkMode),
SizedBox(height: 24.h), SizedBox(height: 16.h),
// 更新说明 - 当有可用更新时显示 // 更新说明 - 当有可用更新时显示
//_buildUpdateNotes(isDarkMode), //_buildUpdateNotes(isDarkMode),
SizedBox(height: 16.h),
// 状态显示区域 // 状态显示区域
Obx(() { Obx(() {
@ -150,12 +150,15 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
], ],
), ),
child: Padding( child: Padding(
padding: EdgeInsets.all(24.w), padding: EdgeInsets.all(20.w),
child: Column( child: Column(
children: [ children: [
// 设备图标区域 // 设备图标区域
_buildDeviceIcon(isDarkMode), _buildDeviceIcon(isDarkMode),
SizedBox(height: 24.h), SizedBox(height: 16.h),
// 设备详细信息区域
_buildDeviceDetailInfo(isDarkMode),
SizedBox(height: 16.h),
// 版本信息区域 // 版本信息区域
_buildVersionInfo(isDarkMode), _buildVersionInfo(isDarkMode),
], ],
@ -167,7 +170,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
// 设备图标 // 设备图标
Widget _buildDeviceIcon(bool isDarkMode) { Widget _buildDeviceIcon(bool isDarkMode) {
return Container( return Container(
padding: EdgeInsets.all(20.w), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withValues(alpha: 0.05) ? Colors.white.withValues(alpha: 0.05)
@ -184,12 +187,193 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
isDarkMode isDarkMode
? 'assets/images/headphone_dark.png' ? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png', : 'assets/images/headphone_light.png',
height: 120.h, height: 100.h,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
); );
} }
// 设备详细信息显示
Widget _buildDeviceDetailInfo(bool isDarkMode) {
return Obx(() {
final deviceInfo = Get.find<BleManager>().deviceInfo.value;
return Container(
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withValues(alpha: 0.03)
: Colors.white.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isDarkMode
? Colors.white.withValues(alpha: 0.08)
: Colors.grey.withValues(alpha: 0.2),
width: 1,
),
),
child: Column(
children: [
// 设备型号信息
_buildDeviceInfoRow(
icon: Icons.headset,
label: '设备型号',
value: deviceInfo?.name ?? '未知设备',
isDarkMode: isDarkMode,
),
// SizedBox(height: 10.h),
// // 电量信息区域
// Row(
// children: [
// // 左耳电量
// Expanded(
// child: _buildBatteryInfo(
// label: '左耳电量',
// batteryLevel: deviceInfo?.leftEarBattery ?? 0,
// isCharging: deviceInfo?.leftEarCharging ?? false,
// isDarkMode: isDarkMode,
// icon: Icons.headset,
// ),
// ),
// SizedBox(width: 10.w),
// // 右耳电量
// Expanded(
// child: _buildBatteryInfo(
// label: '右耳电量',
// batteryLevel: deviceInfo?.rightEarBattery ?? 0,
// isCharging: deviceInfo?.rightEarCharging ?? false,
// isDarkMode: isDarkMode,
// icon: Icons.headset,
// ),
// ),
// ],
// ),
],
),
);
});
}
// 设备信息行组件
Widget _buildDeviceInfoRow({
required IconData icon,
required String label,
required String value,
required bool isDarkMode,
}) {
return Row(
children: [
Container(
padding: EdgeInsets.all(6.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.blue.withValues(alpha: 0.1)
: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6.r),
),
child: Icon(
icon,
size: 16.sp,
color: isDarkMode ? Colors.blue[300] : Colors.blue[600],
),
),
SizedBox(width: 12.w),
Text(
label,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
const Spacer(),
Text(
value,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
],
);
}
// 电池信息组件
Widget _buildBatteryInfo({
required String label,
required int batteryLevel,
required bool isCharging,
required bool isDarkMode,
required IconData icon,
}) {
Color batteryColor;
if (batteryLevel > 60) {
batteryColor = Colors.green;
} else if (batteryLevel > 30) {
batteryColor = Colors.orange;
} else {
batteryColor = Colors.red;
}
return Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: isDarkMode
? batteryColor.withValues(alpha: 0.1)
: batteryColor.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: batteryColor.withValues(alpha: 0.3),
width: 1,
),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isCharging ? Icons.battery_charging_full : Icons.battery_std,
size: 16.sp,
color: batteryColor,
),
SizedBox(width: 4.w),
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
],
),
SizedBox(height: 4.h),
Text(
'${batteryLevel}%',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: batteryColor,
),
),
if (isCharging) ...[
SizedBox(height: 1.h),
Text(
'充电中',
style: TextStyle(
fontSize: 10.sp,
color: batteryColor,
fontWeight: FontWeight.w500,
),
),
],
],
),
);
}
// 版本信息显示 // 版本信息显示
Widget _buildVersionInfo(bool isDarkMode) { Widget _buildVersionInfo(bool isDarkMode) {
return Column( return Column(
@ -201,7 +385,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
isLatest: false, isLatest: false,
), ),
SizedBox(height: 16.h), SizedBox(height: 8.h),
// 最新版本信息 // 最新版本信息
_buildVersionInfoRow( _buildVersionInfoRow(
label: '最新固件版本'.tr, label: '最新固件版本'.tr,
@ -210,7 +394,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
isLatest: true, isLatest: true,
), ),
// 版本状态指示器 // 版本状态指示器
SizedBox(height: 20.h), SizedBox(height: 14.h),
_buildVersionStatusIndicator(isDarkMode), _buildVersionStatusIndicator(isDarkMode),
], ],
); );
@ -224,7 +408,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
required bool isLatest, required bool isLatest,
}) { }) {
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withValues(alpha: 0.03) ? Colors.white.withValues(alpha: 0.03)
@ -252,7 +436,8 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
children: [ children: [
if (isLatest && controller.isUpdateAvailable.value) ...[ if (isLatest && controller.isUpdateAvailable.value) ...[
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), padding:
EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.2), color: Colors.orange.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(8.r),
@ -403,7 +588,9 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
), ),
child: Icon( child: Icon(
Icons.new_releases, Icons.new_releases,
color: isDarkMode ? Colors.amber[300] : Colors.amber[700], color: isDarkMode
? Colors.amber[300]
: Colors.amber[700],
size: 18.sp, size: 18.sp,
), ),
), ),
@ -413,7 +600,9 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.amber[200] : Colors.amber[800], color: isDarkMode
? Colors.amber[200]
: Colors.amber[800],
), ),
), ),
], ],
@ -574,7 +763,8 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
), ),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: isDarkMode ? const Color(0xFF0066CC) : Colors.blue, backgroundColor:
isDarkMode ? const Color(0xFF0066CC) : Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.h), padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -597,6 +787,11 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
return _buildProgressIndicator(isDarkMode); return _buildProgressIndicator(isDarkMode);
} }
// 显示详细状态提示
if (controller.showDetailedStatus.value) {
return _buildDetailedStatusIndicator(isDarkMode);
}
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -685,7 +880,9 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
'取出双耳机,并保证电量均为30%以上的状态'.tr, '取出双耳机,并保证电量均为30%以上的状态'.tr,
style: TextStyle( style: TextStyle(
fontSize: 13.sp, fontSize: 13.sp,
color: isDarkMode ? Colors.orange[200] : Colors.orange[800], color: isDarkMode
? Colors.orange[200]
: Colors.orange[800],
height: 1.3, height: 1.3,
), ),
), ),
@ -700,6 +897,89 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
}); });
} }
// 详细状态指示器
Widget _buildDetailedStatusIndicator(bool isDarkMode) {
return Obx(() {
return Container(
width: double.infinity,
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDarkMode
? [
const Color(0xFF1A1A1A).withValues(alpha: 0.9),
const Color(0xFF2A2A2A).withValues(alpha: 0.9),
]
: [
const Color(0xFFF8FAFF),
const Color(0xFFE3F2FD),
],
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withValues(alpha: 0.3)
: Colors.black.withValues(alpha: 0.08),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
// 状态图标和文字
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: SizedBox(
width: 20.sp,
height: 20.sp,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? const Color(0xFF0066CC) : Colors.blue,
),
),
),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
controller.detailedStatus.value,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.blue[300] : Colors.blue[700],
),
),
),
],
),
SizedBox(height: 16.h),
// 提示信息
Text(
'请保持设备连接,不要关闭应用',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
);
});
}
// 进度指示器 // 进度指示器
Widget _buildProgressIndicator(bool isDarkMode) { Widget _buildProgressIndicator(bool isDarkMode) {
return Obx(() { return Obx(() {

27
lib/modules/pairing/controllers/pairing_controller.dart

@ -431,9 +431,12 @@ class PairingController extends GetxController {
try { try {
Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})'); Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})');
final result = await authbindingDevice(device); final result = await authbindingDevice(device);
if (result == 2) { if (result == 10) {
//验证失败 进入手动授权 //验证失败 进入手动授权
await connectble(device); await connectble(device);
} else if (result == 9) {
Logger.i(_tag, 'BLE已连接,直接完成配对');
_transition(PairingState.completed);
} else if (result == 1) { } else if (result == 1) {
//跳转绑定界面 //跳转绑定界面
//验证失败 进入手动授权 //验证失败 进入手动授权
@ -557,15 +560,22 @@ class PairingController extends GetxController {
//自动绑定设备 0 未合作商 1 合作商 2,已绑定 //自动绑定设备 0 未合作商 1 合作商 2,已绑定
Future<int> authbindingDevice(BluetoothDevice device) async { Future<int> authbindingDevice(BluetoothDevice device) async {
if (User.instance.earphonemac == device.address) { if (User.instance.earphonelicense != "" &&
User.instance.earphonemac == device.address) {
// 已经绑定 // 已经绑定
return 2; return 10;
}
if (User.instance.earphoneauthcode != "" &&
User.instance.earphonemac == device.address) {
// 已经绑定
return 9;
} }
// 检查设备名称是否包含 DeepSound 或 DeapSound // 检查设备名称是否包含 DeepSound 或 DeapSound
final deviceName = device.name; final deviceName = device.name;
print('获取到 deviceName: $deviceName');
if (deviceName.contains('DeepSound') || deviceName.contains('DeapSound')) { final names = AppConfig.allproductNames();
print('获取到 deviceName: $deviceName names:$names');
if (names.contains(deviceName)) {
await _bleManager.startScan(deviceName); await _bleManager.startScan(deviceName);
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
// 尝试最多读取 3 次设备信息,每次间隔 1 秒 // 尝试最多读取 3 次设备信息,每次间隔 1 秒
@ -610,7 +620,7 @@ class PairingController extends GetxController {
Logger.i(_tag, '绑定设备成功'); Logger.i(_tag, '绑定设备成功');
User.instance.earphonemac = currentDevice?.address ?? ''; User.instance.earphonemac = currentDevice?.address ?? '';
User.instance.earphonename = currentDevice?.name ?? ''; User.instance.earphonename = currentDevice?.name ?? '';
User.instance.earphoneauthcode = deviceCode.value; User.instance.earphonelicense = deviceCode.value;
_transition(PairingState.verifying); _transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!); _verifyDevice(_currentClassicDevice!);
return; return;
@ -642,8 +652,9 @@ class PairingController extends GetxController {
void onAutoCodeInputTap() async { void onAutoCodeInputTap() async {
// 调用BLE管理器的自动绑定方法 // 调用BLE管理器的自动绑定方法
final response = await Api.bindauthcode({ final response = await Api.bindauthcode({
"code": codeInputController.text.trim(),
"earphonname": currentDevice?.name,
"earphonemac": currentDevice?.address, "earphonemac": currentDevice?.address,
"earphoneauthcode": codeInputController.text.trim(),
}); });
Logger.i(_tag, '手动授权结果 ${response}'); Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) { if (response != null) {

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

@ -23,9 +23,7 @@ class SettingsController extends GetxController {
// 语言设置 // 语言设置
final List<LanguageModel> languageList = [ final List<LanguageModel> languageList = [
LanguageModel( LanguageModel(
name: 'followingSystem', // 跟随系统.tr name: 'followingSystem', alias: '', value: 'system'), // 跟随系统.tr
alias: '',
value: 'system'),
LanguageModel(name: 'english', alias: 'English', value: 'en_US'), // 英语.tr LanguageModel(name: 'english', alias: 'English', value: 'en_US'), // 英语.tr
LanguageModel(name: 'chinese', alias: '中文', value: 'zh_CN'), // 中文.tr LanguageModel(name: 'chinese', alias: '中文', value: 'zh_CN'), // 中文.tr
LanguageModel( LanguageModel(
@ -78,8 +76,7 @@ class SettingsController extends GetxController {
LanguageModel(name: 'estonian', alias: 'Eesti', value: 'et_EE'), // 爱沙尼亚语 LanguageModel(name: 'estonian', alias: 'Eesti', value: 'et_EE'), // 爱沙尼亚语
LanguageModel( LanguageModel(
name: 'serbian', alias: 'Srpski', value: 'sr_RS'), // 塞尔维亚语(拉丁) name: 'serbian', alias: 'Srpski', value: 'sr_RS'), // 塞尔维亚语(拉丁)
LanguageModel( LanguageModel(name: 'icelandic', alias: 'Íslenska', value: 'is_IS'), // 冰岛语
name: 'icelandic', alias: 'Íslenska', value: 'is_IS'), // 新增冰岛语
]; ];
Rx<LanguageModel> language = LanguageModel().obs; Rx<LanguageModel> language = LanguageModel().obs;

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

@ -203,7 +203,7 @@ class SettingsView extends GetView<SettingsController> {
controller.userInfo.value.viplv] ?? controller.userInfo.value.viplv] ??
'Unknown'; 'Unknown';
return Text( return Text(
membershipString, membershipString.tr, // 会员等级
style: TextStyle( style: TextStyle(
fontSize: 10.sp, fontSize: 10.sp,
color: Colors.white, color: Colors.white,
@ -327,7 +327,7 @@ class SettingsView extends GetView<SettingsController> {
: Colors.cyan[100]!, : Colors.cyan[100]!,
iconColor: iconColor:
isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!, isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: controller.timbreName.value.tr, value: controller.timbreName.value.tr, // 当前音色
valueStyle: TextStyle( valueStyle: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[500], color: isDarkMode ? Colors.white70 : Colors.grey[500],
@ -345,8 +345,8 @@ class SettingsView extends GetView<SettingsController> {
: Colors.grey[200]), : Colors.grey[200]),
// 实时对话入口 // 实时对话入口
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: '实时语音对话', title: 'realtimeVoiceChat'.tr, // 实时语音对话
subtitle: '体验智能语音交互功能(功能预览)', subtitle: 'realtimeVoiceChatInfo'.tr, // 实时语音对话信息
icon: Icons.chat_bubble_outline, icon: Icons.chat_bubble_outline,
iconBgColor: isDarkMode iconBgColor: isDarkMode
? Colors.purple[900]!.withOpacity(0.3) ? Colors.purple[900]!.withOpacity(0.3)
@ -412,7 +412,7 @@ class SettingsView extends GetView<SettingsController> {
Obx( Obx(
() => _buildSimpleNavigationSetting( () => _buildSimpleNavigationSetting(
title: 'languageSetting'.tr, // 语言设置 title: 'languageSetting'.tr, // 语言设置
subtitle: controller.language.value.name.tr, subtitle: controller.language.value.name.tr, // 当前语言
icon: Icons.language, icon: Icons.language,
iconBgColor: isDarkMode iconBgColor: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3) ? Colors.indigo[900]!.withOpacity(0.3)
@ -506,7 +506,7 @@ class SettingsView extends GetView<SettingsController> {
// 清除配对设备 // 清除配对设备
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: 'deviceManagement'.tr, // 设备管理 title: 'deviceManagement'.tr, // 设备管理
subtitle: 'deviceManagementInfo'.tr, // 设备管理列表 subtitle: 'manageConnectedDevices'.tr, // 管理已绑定的蓝牙设备
icon: Icons.settings_bluetooth, icon: Icons.settings_bluetooth,
iconBgColor: isDarkMode iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3) ? Colors.red[900]!.withOpacity(0.3)
@ -633,8 +633,8 @@ class SettingsView extends GetView<SettingsController> {
// : Colors.grey[200]), // : Colors.grey[200]),
// 测试ble耳机固件版本升级 // 测试ble耳机固件版本升级
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: '固件升级'.tr, title: 'firmwareUpgrade'.tr, // 固件升级
subtitle: 'ble耳机固件版本升级'.tr, subtitle: 'bleHeadsetFirmwareUpgrade'.tr, // BLE耳机固件版本升级
icon: Icons.system_update_alt, icon: Icons.system_update_alt,
iconBgColor: isDarkMode iconBgColor: isDarkMode
? Colors.orange[900]!.withOpacity(0.3) ? Colors.orange[900]!.withOpacity(0.3)
@ -651,14 +651,21 @@ class SettingsView extends GetView<SettingsController> {
: Colors.grey[200]), : Colors.grey[200]),
// 添加权限管理设置项 // 添加权限管理设置项
_buildSettingItem( _buildSimpleNavigationSetting(
title: 'permissionManagement'.tr, // 权限管理
subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置
icon: Icons.security, icon: Icons.security,
iconColor: Colors.orange, iconBgColor: isDarkMode
title: '权限管理'.tr, ? Colors.orange[900]!.withOpacity(0.3)
isDarkMode: isDarkMode, : Colors.orange[100]!,
iconColor:
isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () { onTap: () {
Get.toNamed(Routes.permissions); Get.toNamed(Routes.permissions);
}, },
isDarkMode: isDarkMode,
), ),
], ],
), ),
@ -750,8 +757,8 @@ class SettingsView extends GetView<SettingsController> {
Obx(() => Text( Obx(() => Text(
// 如果版本信息为空,显示加载中 // 如果版本信息为空,显示加载中
controller.appVersion.value.isEmpty controller.appVersion.value.isEmpty
? '${'versions'.tr} 加载中...' ? '${'versions'.tr} ${'loading'.tr}' // 版本 加载中
: '${'versions'.tr} ${controller.appVersion.value} (Build ${controller.buildNumber.value})', : '${'versions'.tr} ${controller.appVersion.value} (Build ${controller.buildNumber.value})', // 版本号
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode color: isDarkMode
@ -784,7 +791,7 @@ class SettingsView extends GetView<SettingsController> {
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Text( child: Text(
'checkUpdates'.tr, 'checkUpdates'.tr, // 检查更新
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: color:
@ -825,7 +832,7 @@ class SettingsView extends GetView<SettingsController> {
Get.locale?.countryCode == 'CN') Get.locale?.countryCode == 'CN')
InkWell( InkWell(
onTap: () => Get.to( onTap: () => Get.to(
() => const WebViewPage( () => WebViewPage(
url: 'https://beian.miit.gov.cn', url: 'https://beian.miit.gov.cn',
title: 'ICP备案信息', title: 'ICP备案信息',
), ),
@ -1191,7 +1198,7 @@ class SettingsView extends GetView<SettingsController> {
return Container( return Container(
constraints: BoxConstraints(maxWidth: 120.w), // 限制最大宽度 constraints: BoxConstraints(maxWidth: 120.w), // 限制最大宽度
child: Text( child: Text(
'$serviceName'.tr, '$serviceName'.tr, // 语音服务名称
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600], color: isDarkMode ? Colors.white70 : Colors.grey[600],
@ -1284,7 +1291,7 @@ class SettingsView extends GetView<SettingsController> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'${service['name']}'.tr, '${service['name']}'.tr, // 服务名称
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -1296,7 +1303,7 @@ class SettingsView extends GetView<SettingsController> {
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
'${service['description']}'.tr, '${service['description']}'.tr, // 服务描述
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode color: isDarkMode
@ -1421,7 +1428,7 @@ class SettingsView extends GetView<SettingsController> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
language.name.tr, language.name.tr, // 语言名称
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -1433,7 +1440,7 @@ class SettingsView extends GetView<SettingsController> {
), ),
), ),
Text( Text(
language.alias, // 这里使用原始名称,不加 .tr language.alias, // 语言别名(原始名称,不加.tr)
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: isDarkMode color: isDarkMode

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

@ -27,6 +27,8 @@ enum AudioOutputDevice {
} }
class TranslationController extends GetxController { class TranslationController extends GetxController {
static const String TAG = 'TranslationController';
// 服务 // 服务
final AsrService _asrService = Get.find<AsrService>(); final AsrService _asrService = Get.find<AsrService>();
final VolcanoTranslationService _translationService = final VolcanoTranslationService _translationService =
@ -59,6 +61,14 @@ class TranslationController extends GetxController {
// 主页面显示限制:只显示最近20条,保证界面性能和用户体验 // 主页面显示限制:只显示最近20条,保证界面性能和用户体验
static const int _maxDisplayItems = 20; static const int _maxDisplayItems = 20;
// 分页加载相关状态
final isLoadingMore = false.obs; // 是否正在加载更多历史记录
final hasMoreHistory = true.obs; // 是否还有更多历史记录
final currentPage = 1.obs; // 当前页码
final pageSize = 20; // 每页记录数量
final isLoadingMoreError = false.obs; // 加载更多时是否出错
bool _scrollListenerSetup = false; // 滚动监听器设置标志
RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2 RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2
RxInt lastActiveSpeaker = 0.obs; // 上一次状态 RxInt lastActiveSpeaker = 0.obs; // 上一次状态
bool isContinuePlaying = false; // 是否继续播放 bool isContinuePlaying = false; // 是否继续播放
@ -181,11 +191,9 @@ class TranslationController extends GetxController {
// 初始化服务 // 初始化服务
_initServices(); _initServices();
suspendOtherServices();
// 使用微任务在布局完成后自动滚动到底部 // 🔑 reverse模式下不需要手动滚动,ListView会自然显示最新内容
WidgetsBinding.instance.addPostFrameCallback((_) { // 参考Agent模块的实现模式
_scrollToBottom();
});
} }
// 加载保存的语言设置 // 加载保存的语言设置
@ -754,18 +762,28 @@ class TranslationController extends GetxController {
} }
} }
// 滚动到底部 // 滚动到底部(适配reverse模式)
void _scrollToBottom() { void _scrollToBottom({bool animate = true}) {
if (scrollController.hasClients && if (scrollController.hasClients &&
(translationHistory.isNotEmpty || currentSessionId != null)) { (translationHistory.isNotEmpty || currentSessionId != null)) {
try { try {
scrollController.animateTo( // 🔑 reverse模式下:滚动到顶部(position = 0)来显示最新消息
scrollController.position.maxScrollExtent, // 正常模式下:滚动到底部(maxScrollExtent)来显示最新消息
duration: const Duration(milliseconds: 300), const targetPosition = 0.0; // reverse模式下最新消息在顶部
curve: Curves.easeOut,
); if (animate) {
// 有动画的滚动,用于新消息添加时
scrollController.animateTo(
targetPosition,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} else {
// 无动画的跳转,用于初始化时
scrollController.jumpTo(targetPosition);
}
} catch (e) { } catch (e) {
Logger.error('滚动到底部失败: ${e.toString()}'); Logger.error('滚动到顶部失败(reverse模式): ${e.toString()}');
} }
} }
} }
@ -879,22 +897,37 @@ class TranslationController extends GetxController {
// 【注意】存储中的记录都是完成态记录(isIntermediate = false) // 【注意】存储中的记录都是完成态记录(isIntermediate = false)
// 因为保存时已经过滤掉了中间态记录,确保数据质量 // 因为保存时已经过滤掉了中间态记录,确保数据质量
// 初始化分页状态
currentPage.value = 1;
isLoadingMore.value = false;
isLoadingMoreError.value = false;
// 主页面只显示最近的20条完成态记录,保证界面性能 // 主页面只显示最近的20条完成态记录,保证界面性能
if (allHistory.length > _maxDisplayItems) { if (allHistory.length > _maxDisplayItems) {
translationHistory.value = translationHistory.value =
allHistory.sublist(allHistory.length - _maxDisplayItems); allHistory.sublist(allHistory.length - _maxDisplayItems);
// 检查是否还有更多历史记录
hasMoreHistory.value = true;
} else { } else {
translationHistory.value = allHistory; translationHistory.value = allHistory;
hasMoreHistory.value = false;
} }
// 使用微任务确保在视图完全渲染后滚动到底部 // 设置滚动监听器
WidgetsBinding.instance.addPostFrameCallback((_) { _setupScrollListener();
_scrollToBottom();
}); // 🔑 reverse模式下不需要滚动到底部,ListView会自然显示最新记录
// 参考Agent模块:使用reverse:true时不需要手动滚动到底部
} catch (e) { } catch (e) {
_storage.remove(_historyKey); _storage.remove(_historyKey);
Logger.error('加载翻译历史失败: ${e.toString()}'); Logger.error('加载翻译历史失败: ${e.toString()}');
} }
} else {
// 没有历史数据时也要初始化分页状态
hasMoreHistory.value = false;
currentPage.value = 1;
isLoadingMore.value = false;
isLoadingMoreError.value = false;
} }
} }
@ -924,6 +957,112 @@ class TranslationController extends GetxController {
} }
} }
// 设置滚动监听器(适配reverse模式)
void _setupScrollListener() {
if (_scrollListenerSetup) return; // 防止重复设置
scrollController.addListener(() {
// 🔑 reverse模式下:检查是否滚动到底部(实际是历史记录的顶部)
// 参考Agent模块的实现
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 100) {
// 当距离底部100像素时触发加载更多历史记录
if (hasMoreHistory.value && !isLoadingMore.value) {
Logger.d(TAG, '触发加载更多翻译历史记录(reverse模式)');
loadMoreHistory();
}
}
});
_scrollListenerSetup = true;
}
// 加载更多历史记录(分页加载)
Future<void> loadMoreHistory() async {
// 防止重复加载
if (isLoadingMore.value || !hasMoreHistory.value) {
Logger.d(TAG,
'跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreHistory=${hasMoreHistory.value}');
return;
}
try {
isLoadingMore.value = true;
isLoadingMoreError.value = false;
final nextPage = currentPage.value + 1;
Logger.i(TAG, '正在加载第 $nextPage 页翻译历史...');
// 从存储中获取所有历史记录
final historyJson = _storage.read(_historyKey);
if (historyJson != null) {
final List<dynamic> historyList = jsonDecode(historyJson);
final List<TranslationItem> allHistory =
historyList.map((item) => TranslationItem.fromJson(item)).toList();
// 计算分页参数
final totalItems = allHistory.length;
final totalPages = (totalItems / pageSize).ceil();
// 修复分页索引计算:从最新记录向前分页
// 第1页:最新20条 (index: totalItems-20 到 totalItems)
// 第2页:接下来20条 (index: totalItems-40 到 totalItems-20)
// 第3页:再接下来20条 (index: totalItems-60 到 totalItems-40)
final endIndex = totalItems - ((nextPage - 1) * pageSize);
final startIndex = totalItems - (nextPage * pageSize);
Logger.d(TAG,
'分页计算: 总记录=$totalItems, 总页数=$totalPages, 当前页=$nextPage, startIndex=$startIndex, endIndex=$endIndex');
if (endIndex > 0) {
// 获取当前页的数据,确保索引不越界
final actualStartIndex = startIndex < 0 ? 0 : startIndex;
final actualEndIndex = endIndex > totalItems ? totalItems : endIndex;
final pageItems =
allHistory.sublist(actualStartIndex, actualEndIndex);
Logger.d(TAG,
'实际索引: start=$actualStartIndex, end=$actualEndIndex, 获取${pageItems.length}条记录');
if (pageItems.isNotEmpty) {
// 🔑 简化延迟,参考Agent模块(300ms vs 1000ms)
await Future.delayed(const Duration(milliseconds: 300));
// 🔑 reverse模式下:将新记录插入到现有记录列表的开头
// 由于reverse显示,用户看到的顺序不会改变,无需复杂的位置保持
final updatedHistory = [...pageItems, ...translationHistory];
translationHistory.assignAll(updatedHistory);
// 更新分页状态
currentPage.value = nextPage;
// 修复hasMoreHistory判断:当startIndex <= 0时表示已经到达最早的记录
hasMoreHistory.value = startIndex > 0;
Logger.i(TAG,
'成功加载 ${pageItems.length} 条历史记录,当前页: $nextPage/$totalPages, 还有更多: ${hasMoreHistory.value}');
} else {
// 没有更多记录
hasMoreHistory.value = false;
Logger.i(TAG, '没有更多翻译历史记录');
}
} else {
// 索引超出范围,没有更多记录
hasMoreHistory.value = false;
Logger.i(TAG, '已到达历史记录开头');
}
} else {
// 没有存储数据
hasMoreHistory.value = false;
Logger.i(TAG, '存储中没有更多历史记录');
}
} catch (e) {
Logger.e(TAG, '加载更多历史记录失败: $e');
isLoadingMoreError.value = true;
} finally {
isLoadingMore.value = false;
}
}
// 获取所有完成态历史记录(供历史页面使用) // 获取所有完成态历史记录(供历史页面使用)
// 【修复】简化实现,合并存储和内存中的完成态记录 // 【修复】简化实现,合并存储和内存中的完成态记录
List<TranslationItem> getAllHistory() { List<TranslationItem> getAllHistory() {
@ -1060,7 +1199,7 @@ class TranslationController extends GetxController {
// 滚动到底部 (公共方法,用于向后兼容) // 滚动到底部 (公共方法,用于向后兼容)
void scrollToBottom({bool animate = true}) { void scrollToBottom({bool animate = true}) {
_scrollToBottom(); _scrollToBottom(animate: animate);
} }
} }

188
lib/modules/translation/views/translation_detail_view.dart

@ -35,9 +35,10 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), backgroundColor:
isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: _buildAppBar(isDarkMode), appBar: _buildAppBar(isDarkMode),
body: Obx(() { body: Obx(() {
// 显示加载状态 // 显示加载状态
@ -48,7 +49,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
return Column( return Column(
children: [ children: [
_buildTabButtons(isDarkMode), _buildTabButtons(isDarkMode),
@ -58,11 +59,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}), }),
); );
} }
// 构建应用栏 // 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), backgroundColor:
isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8),
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon( icon: Icon(
@ -93,7 +95,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
], ],
); );
} }
// 构建选项卡按钮 // 构建选项卡按钮
Widget _buildTabButtons(bool isDarkMode) { Widget _buildTabButtons(bool isDarkMode) {
return Padding( return Padding(
@ -102,11 +104,11 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
children: [ children: [
Expanded( Expanded(
child: Obx(() => _buildTabButton( child: Obx(() => _buildTabButton(
title: 'originalRecord'.tr, // 原始记录 title: 'originalRecord'.tr, // 原始记录
isSelected: controller.selectedTabIndex.value == 0, isSelected: controller.selectedTabIndex.value == 0,
onTap: () => controller.switchTab(0), onTap: () => controller.switchTab(0),
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
)), )),
), ),
//屏蔽“记录摘要”,需要是开启 //屏蔽“记录摘要”,需要是开启
// SizedBox(width: 12.w), // SizedBox(width: 12.w),
@ -122,7 +124,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 构建内容区域 // 构建内容区域
Widget _buildContentArea(bool isDarkMode) { Widget _buildContentArea(bool isDarkMode) {
return Expanded( return Expanded(
@ -134,13 +136,13 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
children: [ children: [
_buildTimestamp(isDarkMode), _buildTimestamp(isDarkMode),
SizedBox(height: 8.h), SizedBox(height: 8.h),
// 根据选中的选项卡显示不同内容 // 根据选中的选项卡显示不同内容
if (controller.selectedTabIndex.value == 0) if (controller.selectedTabIndex.value == 0)
..._buildOriginalRecords(isDarkMode) ..._buildOriginalRecords(isDarkMode)
else else
_buildSummaryWidget(isDarkMode), _buildSummaryWidget(isDarkMode),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
); );
@ -148,7 +150,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 构建时间戳 // 构建时间戳
Widget _buildTimestamp(bool isDarkMode) { Widget _buildTimestamp(bool isDarkMode) {
return Padding( return Padding(
@ -164,12 +166,14 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 构建原始记录列表 // 构建原始记录列表
List<Widget> _buildOriginalRecords(bool isDarkMode) { List<Widget> _buildOriginalRecords(bool isDarkMode) {
return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)).toList(); return controller.sessionItems
.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode))
.toList();
} }
// 构建选项卡按钮 // 构建选项卡按钮
Widget _buildTabButton({ Widget _buildTabButton({
required String title, required String title,
@ -182,8 +186,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: Container( child: Container(
height: 40.h, height: 40.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? (isDarkMode ? AppColors.primary : Colors.blue) ? (isDarkMode ? AppColors.primary : Colors.blue)
: (isDarkMode ? AppColors.cardBackground : Colors.white), : (isDarkMode ? AppColors.cardBackground : Colors.white),
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
boxShadow: [ boxShadow: [
@ -200,15 +204,15 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected color: isSelected
? Colors.white ? Colors.white
: (isDarkMode ? Colors.white : Colors.black87), : (isDarkMode ? Colors.white : Colors.black87),
), ),
), ),
), ),
); );
} }
// 构建会话详情中的翻译项 // 构建会话详情中的翻译项
Widget _buildSessionItemWidget(TranslationItem item, bool isDarkMode) { Widget _buildSessionItemWidget(TranslationItem item, bool isDarkMode) {
final context = Get.context!; final context = Get.context!;
@ -218,7 +222,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: item.isIntermediate color: item.isIntermediate
? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色
: (item.sourceLanguageCode == controller.currentItem.sourceLanguageCode : (item.sourceLanguageCode ==
controller.currentItem.sourceLanguageCode
? Theme.of(context) ? Theme.of(context)
.colorScheme .colorScheme
.primary .primary
@ -238,13 +243,35 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 源文本 Row(
Text( crossAxisAlignment: CrossAxisAlignment.start,
item.sourceText, children: [
style: TextStyle( // 源文本
fontSize: 16, Expanded(
color: Theme.of(context).textTheme.bodyLarge?.color, child: Text(
), item.sourceText,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
)),
// 删除按钮
if (!item.isIntermediate)
InkWell(
onTap: () => _showDeleteItemConfirmDialog(item),
borderRadius: BorderRadius.circular(6.r),
splashColor: Colors.red.withValues(alpha: 0.1),
highlightColor: Colors.red.withValues(alpha: 0.05),
child: Container(
padding: EdgeInsets.all(6.w),
child: Icon(
Icons.delete_outline,
size: 16.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
),
),
),
],
), ),
if (item.translatedText.isNotEmpty) ...[ if (item.translatedText.isNotEmpty) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@ -273,22 +300,6 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// 删除按钮
if (!item.isIntermediate)
InkWell(
onTap: () => _showDeleteItemConfirmDialog(item),
borderRadius: BorderRadius.circular(6.r),
splashColor: Colors.red.withValues(alpha: 0.1),
highlightColor: Colors.red.withValues(alpha: 0.05),
child: Container(
padding: EdgeInsets.all(6.w),
child: Icon(
Icons.delete_outline,
size: 16.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
),
),
),
// 播放按钮 // 播放按钮
if (!item.isIntermediate) ...[ if (!item.isIntermediate) ...[
SizedBox(width: 8.w), SizedBox(width: 8.w),
@ -310,25 +321,25 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
); );
} }
// 构建总结视图 // 构建总结视图
Widget _buildSummaryWidget(bool isDarkMode) { Widget _buildSummaryWidget(bool isDarkMode) {
final String combinedSourceText = _getCombinedSourceText(); final String combinedSourceText = _getCombinedSourceText();
return Column( return Column(
children: [ children: [
// 摘要内容容器 // 摘要内容容器
_buildSummaryContainer(isDarkMode), _buildSummaryContainer(isDarkMode),
// 提取摘要按钮 // 提取摘要按钮
Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)), Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)),
// 重新生成按钮 // 重新生成按钮
Obx(() => _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), Obx(() =>
_buildRegenerateSummaryButton(combinedSourceText, isDarkMode)),
], ],
); );
} }
// 构建摘要容器 // 构建摘要容器
Widget _buildSummaryContainer(bool isDarkMode) { Widget _buildSummaryContainer(bool isDarkMode) {
return Container( return Container(
@ -354,7 +365,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}), }),
); );
} }
// 构建空摘要提示 // 构建空摘要提示
Widget _buildEmptySummaryPrompt(bool isDarkMode) { Widget _buildEmptySummaryPrompt(bool isDarkMode) {
return Padding( return Padding(
@ -372,7 +383,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 构建摘要内容 // 构建摘要内容
Widget _buildSummaryContent(bool isDarkMode) { Widget _buildSummaryContent(bool isDarkMode) {
return Column( return Column(
@ -417,9 +428,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => controller.playText(controller.summaryResult.value), onTap: () =>
controller.playText(controller.summaryResult.value),
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2), splashColor: isDarkMode
? AppColors.primary.withOpacity(0.2)
: Colors.blue.withOpacity(0.2),
child: Padding( child: Padding(
padding: EdgeInsets.all(8.r), padding: EdgeInsets.all(8.r),
child: Icon( child: Icon(
@ -437,7 +451,9 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: InkWell( child: InkWell(
onTap: _copySummaryToClipboard, onTap: _copySummaryToClipboard,
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2), splashColor: isDarkMode
? AppColors.primary.withOpacity(0.2)
: Colors.blue.withOpacity(0.2),
child: Padding( child: Padding(
padding: EdgeInsets.all(8.r), padding: EdgeInsets.all(8.r),
child: Icon( child: Icon(
@ -454,7 +470,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
], ],
); );
} }
// 复制摘要到剪贴板 // 复制摘要到剪贴板
void _copySummaryToClipboard() { void _copySummaryToClipboard() {
Clipboard.setData(ClipboardData(text: controller.summaryResult.value)); Clipboard.setData(ClipboardData(text: controller.summaryResult.value));
@ -465,16 +481,19 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
duration: Duration(seconds: 2), duration: Duration(seconds: 2),
); );
} }
// 构建提取摘要按钮 // 构建提取摘要按钮
Widget _buildExtractSummaryButton(String combinedSourceText, bool isDarkMode) { Widget _buildExtractSummaryButton(
final bool isDisabled = controller.isGeneratingSummary.value || controller.summaryResult.value.isNotEmpty; String combinedSourceText, bool isDarkMode) {
final bool isDisabled = controller.isGeneratingSummary.value ||
controller.summaryResult.value.isNotEmpty;
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
child: ElevatedButton( child: ElevatedButton(
onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText), onPressed:
isDisabled ? null : () => _extractSummary(combinedSourceText),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: isDarkMode ? AppColors.primary : Colors.blue, backgroundColor: isDarkMode ? AppColors.primary : Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@ -483,14 +502,18 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
elevation: 0, elevation: 0,
disabledBackgroundColor: controller.isGeneratingSummary.value disabledBackgroundColor: controller.isGeneratingSummary.value
? isDarkMode ? AppColors.primary.withAlpha(128) : Colors.blue.withAlpha(128) ? isDarkMode
? AppColors.primary.withAlpha(128)
: Colors.blue.withAlpha(128)
: Colors.grey.withAlpha(128), : Colors.grey.withAlpha(128),
), ),
child: controller.isGeneratingSummary.value child: controller.isGeneratingSummary.value
? _buildGeneratingIndicator(isDarkMode) ? _buildGeneratingIndicator(isDarkMode)
: Text( : Text(
controller.summaryResult.value.isEmpty ? 'extractSummary'.tr : 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要 controller.summaryResult.value.isEmpty
? 'extractSummary'.tr
: 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -499,7 +522,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 构建生成中指示器 // 构建生成中指示器
Widget _buildGeneratingIndicator(bool isDarkMode) { Widget _buildGeneratingIndicator(bool isDarkMode) {
return Row( return Row(
@ -524,13 +547,15 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
], ],
); );
} }
// 构建重新生成按钮 // 构建重新生成按钮
Widget _buildRegenerateSummaryButton(String combinedSourceText, bool isDarkMode) { Widget _buildRegenerateSummaryButton(
if (controller.summaryResult.value.isEmpty || controller.isGeneratingSummary.value) { String combinedSourceText, bool isDarkMode) {
if (controller.summaryResult.value.isEmpty ||
controller.isGeneratingSummary.value) {
return SizedBox.shrink(); return SizedBox.shrink();
} }
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
@ -554,17 +579,17 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
); );
} }
// 提取摘要方法 // 提取摘要方法
void _extractSummary(String text) async { void _extractSummary(String text) async {
final result = await controller.extractSummary(text); final result = await controller.extractSummary(text);
// 检查是否超时,如果超时则显示重试选项 // 检查是否超时,如果超时则显示重试选项
if (result.contains('超时')) { if (result.contains('超时')) {
_showTimeoutDialog(text); _showTimeoutDialog(text);
} }
} }
// 显示超时对话框 // 显示超时对话框
void _showTimeoutDialog(String text) { void _showTimeoutDialog(String text) {
Get.dialog( Get.dialog(
@ -720,11 +745,13 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
// 分享原始记录 // 分享原始记录
final String sourceText = _getCombinedSourceText(); final String sourceText = _getCombinedSourceText();
final String translatedText = _getCombinedTranslatedText(); final String translatedText = _getCombinedTranslatedText();
shareText = '${'originalText'.tr}:\n$sourceText\n\n${'translatedText'.tr}:\n$translatedText'; // 原文:译文: shareText =
'${'originalText'.tr}:\n$sourceText\n\n${'translatedText'.tr}:\n$translatedText'; // 原文:译文:
} else { } else {
// 分享摘要 // 分享摘要
if (controller.summaryResult.value.isNotEmpty) { if (controller.summaryResult.value.isNotEmpty) {
shareText = '${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要: shareText =
'${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要:
} else { } else {
Get.snackbar( Get.snackbar(
'cannotShare'.tr, // 无法分享 'cannotShare'.tr, // 无法分享
@ -735,11 +762,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
return; return;
} }
} }
// 调用系统分享 // 调用系统分享
Share.share( Share.share(
shareText, shareText,
subject: '${'translationSessionRecord'.tr} - ${DateFormat('yyyy-MM-dd HH:mm').format(controller.currentItem.timestamp)}', // 翻译会话记录 subject:
'${'translationSessionRecord'.tr} - ${DateFormat('yyyy-MM-dd HH:mm').format(controller.currentItem.timestamp)}', // 翻译会话记录
); );
} }
} }

91
lib/modules/translation/views/translation_history_view.dart

@ -15,9 +15,10 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), backgroundColor:
isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: _buildAppBar(isDarkMode), appBar: _buildAppBar(isDarkMode),
body: Obx(() { body: Obx(() {
if (!controller.hasHistory) { if (!controller.hasHistory) {
@ -46,7 +47,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final date = controller.sortedDates[index]; final date = controller.sortedDates[index];
final items = controller.groupedHistory.value[date]!; final items = controller.groupedHistory.value[date]!;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -70,9 +71,10 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
}), }),
); );
} }
// 构建日期组容器 // 构建日期组容器
Widget _buildDateGroupContainer(List<TranslationItem> items, bool isDarkMode) { Widget _buildDateGroupContainer(
List<TranslationItem> items, bool isDarkMode) {
return Container( return Container(
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -104,7 +106,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
), ),
); );
} }
// 构建组内的历史记录项 // 构建组内的历史记录项
Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) { Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) {
return InkWell( return InkWell(
@ -117,10 +119,11 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(8.r),
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Row( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( // 主要内容区域
Padding(
padding: EdgeInsets.only(right: 50.w), // 为右侧图标留出空间
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -155,7 +158,8 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
_formatSessionTime(item.timestamp), _formatSessionTime(item.timestamp),
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[600], color:
isDarkMode ? Colors.grey[500] : Colors.grey[600],
), ),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
@ -165,7 +169,9 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
_formatLanguagePair(item), _formatLanguagePair(item),
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[600], color: isDarkMode
? Colors.grey[500]
: Colors.grey[600],
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -175,37 +181,45 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
], ],
), ),
), ),
SizedBox(width: 12.w), // 右上角删除按钮
// 删除会话按钮 Positioned(
InkWell( top: 0,
onTap: () => _showDeleteSessionConfirmDialog(item.sessionId), right: -8.w,
borderRadius: BorderRadius.circular(6.r), child: InkWell(
splashColor: Colors.red.withOpacity(0.1), onTap: () => _showDeleteSessionConfirmDialog(item.sessionId),
highlightColor: Colors.red.withOpacity(0.05), borderRadius: BorderRadius.circular(6.r),
child: Container( splashColor: Colors.red.withOpacity(0.1),
padding: EdgeInsets.all(8.w), highlightColor: Colors.red.withOpacity(0.05),
child: Icon( child: Container(
Icons.delete_outline, padding: EdgeInsets.all(8.w),
color: isDarkMode ? Colors.grey[500] : Colors.grey[400], child: Icon(
size: 18.sp, Icons.delete_outline,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
size: 18.sp,
),
), ),
), ),
), ),
SizedBox(width: 8.w), // 右下角箭头图标
Icon( Positioned(
Icons.chevron_right, bottom: 0,
color: isDarkMode ? Colors.grey[600] : Colors.grey[350], right: 0.w, // 与删除图标垂直对齐
size: 20.sp, child: Icon(
Icons.chevron_right,
color: isDarkMode ? Colors.grey[600] : Colors.grey[350],
size: 20.sp,
),
), ),
], ],
), ),
), ),
); );
} }
void _showTranslationDetail(TranslationItem item) { void _showTranslationDetail(TranslationItem item) {
// 获取该会话的所有翻译项 // 获取该会话的所有翻译项
final List<TranslationItem> sessionItems = controller.getSessionItems(item.sessionId); final List<TranslationItem> sessionItems =
controller.getSessionItems(item.sessionId);
Get.to( Get.to(
() => TranslationDetailView(), () => TranslationDetailView(),
@ -238,8 +252,12 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
// 获取源语言和目标语言的中文名称 // 获取源语言和目标语言的中文名称
final sourceName = languageManager.getChineseNameByAsrCode(item.sourceLanguageCode) ?? 'unknownLanguage'.tr; // 未知语言 final sourceName =
final targetName = languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ?? 'unknownLanguage'.tr; // 未知语言 languageManager.getChineseNameByAsrCode(item.sourceLanguageCode) ??
'unknownLanguage'.tr; // 未知语言
final targetName =
languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ??
'unknownLanguage'.tr; // 未知语言
// 使用双向箭头符号表示翻译方向 // 使用双向箭头符号表示翻译方向
return '$sourceName ⇄ $targetName'; return '$sourceName ⇄ $targetName';
@ -355,11 +373,12 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
), ),
); );
} }
// 构建应用栏 // 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), backgroundColor:
isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8),
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon( icon: Icon(
@ -392,4 +411,4 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
], ],
); );
} }
} }

344
lib/modules/translation/views/translation_view.dart

@ -109,6 +109,7 @@ class TranslationView extends GetView<TranslationController> {
)), )),
centerTitle: true, centerTitle: true,
actions: [ actions: [
Obx(() { Obx(() {
if (controller.isRecording.value) { if (controller.isRecording.value) {
// 录音中:显示红色计时器样式,点击可停止录音 // 录音中:显示红色计时器样式,点击可停止录音
@ -169,19 +170,19 @@ class TranslationView extends GetView<TranslationController> {
}, },
tooltip: 'viewAudioFiles'.tr, // 查看录音文件 tooltip: 'viewAudioFiles'.tr, // 查看录音文件
), ),
// 添加历史记录按钮 // 历史记录按钮
IconButton( // IconButton(
icon: Icon( // icon: Icon(
Icons.history, // Icons.history,
color: isDarkMode ? Colors.white : Colors.black, // color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp, // size: 24.sp,
), // ),
onPressed: () async { // onPressed: () async {
await controller.stopAll(); // await controller.stopAll();
Get.toNamed(Routes.translationHistory); // Get.toNamed(Routes.translationHistory);
}, // },
tooltip: 'translationHistory'.tr, // 翻译历史 // tooltip: 'translationHistory'.tr, // 翻译历史
), // ),
], ],
); );
} }
@ -289,10 +290,12 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildTranslationContent(bool isDarkMode) { Widget _buildTranslationContent(bool isDarkMode) {
return Obx(() { return Obx(() {
final itemCount = controller.translationHistory.length; final itemCount = controller.translationHistory.length +
(controller.hasMoreHistory.value ? 1 : 0);
return ListView.builder( return ListView.builder(
controller: controller.scrollController, controller: controller.scrollController,
reverse: true, // 🔑 关键优化:采用reverse模式,参考Agent模块
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 16.w, left: 16.w,
right: 16.w, right: 16.w,
@ -301,8 +304,21 @@ class TranslationView extends GetView<TranslationController> {
), ),
itemCount: itemCount, itemCount: itemCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
// 如果是最后一项且有更多历史记录,显示加载指示器(reverse模式下)
if (index == controller.translationHistory.length && controller.hasMoreHistory.value) {
return _buildLoadMoreIndicator();
}
// reverse模式下需要反转索引来获取正确的历史记录
final historyIndex = controller.translationHistory.length - 1 - index;
// 检查索引是否有效(reverse模式下的索引验证)
if (historyIndex < 0 || historyIndex >= controller.translationHistory.length) {
return const SizedBox.shrink();
}
// 获取当前项 // 获取当前项
final historyItem = controller.translationHistory[index]; final historyItem = controller.translationHistory[historyIndex];
// 构建项目widget // 构建项目widget
return _buildTranslationItemWidget(context, historyItem); return _buildTranslationItemWidget(context, historyItem);
@ -339,13 +355,34 @@ class TranslationView extends GetView<TranslationController> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 源文本 // 源文本和删除按钮行
Text( Row(
item.sourceText, crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: [
fontSize: 16, // 源文本
color: Theme.of(context).textTheme.bodyLarge?.color, Expanded(
), child: Text(
item.sourceText,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
),
),
// 删除按钮 - 只对完成态记录显示,与源文本平齐
if (!item.isIntermediate)
GestureDetector(
onTap: () => _showDeleteItemConfirmDialog(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.delete_outline,
size: 18.sp,
color: Colors.grey[500],
),
),
),
],
), ),
if (item.translatedText.isNotEmpty) ...[ if (item.translatedText.isNotEmpty) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@ -364,20 +401,26 @@ class TranslationView extends GetView<TranslationController> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Expanded(
'${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', child: Text(
style: TextStyle( '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}',
fontSize: 12, style: TextStyle(
color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
), ),
), ),
// 播放按钮
if (!item.isIntermediate) if (!item.isIntermediate)
GestureDetector( GestureDetector(
onTap: () => controller.playTranslationItem(item), onTap: () => controller.playTranslationItem(item),
child: Icon( child: Container(
Icons.volume_up, padding: EdgeInsets.all(4.w),
size: 20, child: Icon(
color: Theme.of(context).colorScheme.primary, Icons.volume_up,
size: 20.sp,
color: Theme.of(context).colorScheme.primary,
),
), ),
), ),
], ],
@ -716,4 +759,243 @@ class TranslationView extends GetView<TranslationController> {
), ),
); );
} }
// 构建加载更多指示器
Widget _buildLoadMoreIndicator() {
return Obx(() {
if (controller.isLoadingMore.value) {
// 正在加载状态 - 增强视觉效果
return Container(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.primary,
),
),
),
const SizedBox(height: 12),
Text(
'正在加载历史记录...',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
],
),
),
);
} else if (controller.isLoadingMoreError.value) {
// 加载失败状态 - 增强视觉效果
return Container(
padding: const EdgeInsets.symmetric(vertical: 18),
child: Center(
child: GestureDetector(
onTap: () => controller.loadMoreHistory(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.red[200]!, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh,
size: 18.sp,
color: Colors.red[600],
),
SizedBox(width: 8.w),
Text(
'加载失败,点击重试',
style: TextStyle(
fontSize: 14.sp,
color: Colors.red[600],
fontWeight: FontWeight.w500,
),
),
],
),
),
),
),
);
} else {
// 默认状态(有更多记录但未加载)- 增强视觉效果
return Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.keyboard_arrow_up,
size: 16.sp,
color: Colors.grey[500],
),
SizedBox(width: 4.w),
Text(
'向上滑动查看更多历史',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[600],
),
),
],
),
),
),
);
}
});
}
// 显示删除单条记录确认对话框
void _showDeleteItemConfirmDialog(TranslationItem item) {
final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark;
Get.dialog(
AlertDialog(
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
title: Text(
'deleteTranslationRecord'.tr, // 删除翻译记录
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black,
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'deleteTranslationConfirm'.tr, // 确定要删除这条翻译记录吗?此操作无法撤销。
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.grey[300] : Colors.black87,
),
),
SizedBox(height: 12.h),
// 显示要删除的记录预览
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]?.withOpacity(0.5)
: Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.sourceText,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (item.translatedText.isNotEmpty) ...[
SizedBox(height: 4.h),
Text(
item.translatedText,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
],
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr,
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
),
),
),
TextButton(
onPressed: () {
Get.back();
// 删除指定的翻译记录
_deleteTranslationItem(item);
},
child: Text(
'confirm'.tr,
style: TextStyle(
fontSize: 16.sp,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
);
}
// 删除翻译记录的具体实现
void _deleteTranslationItem(TranslationItem item) {
try {
// 调用控制器的删除方法
controller.deleteTranslationItem(item.sessionId, item.timestamp);
// 显示删除成功提示
Get.snackbar(
'删除成功', // deleteSuccess
'已删除该翻译记录', // deleteTranslationSuccess
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Get.isDarkMode ? Colors.grey[800] : Colors.grey[200],
colorText: Get.isDarkMode ? Colors.white : Colors.black,
duration: const Duration(seconds: 2),
);
} catch (e) {
// 显示删除失败提示
Get.snackbar(
'删除失败', // deleteFailed
'删除翻译记录时发生错误: ${e.toString()}', // deleteTranslationError
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[400],
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
}
}
} }

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

@ -734,6 +734,7 @@ object AgentService : CoroutineScope {
) { ) {
Log.d(TAG, "内部方法:通用的ChatAPI处理逻辑") Log.d(TAG, "内部方法:通用的ChatAPI处理逻辑")
// 如果有正在进行的AI流式输出,先停止它 // 如果有正在进行的AI流式输出,先停止它
audioPlayer?.stopAudio()
stopAiStream() stopAiStream()
stopTts() stopTts()
// 记录新的AI任务 // 记录新的AI任务
@ -748,6 +749,7 @@ object AgentService : CoroutineScope {
val responseBuilder = StringBuilder() val responseBuilder = StringBuilder()
var aiMetadata:String = "" var aiMetadata:String = ""
var userMetadata = JSONObject() var userMetadata = JSONObject()
var reply = false; //是否回复
var broadcast = true //是否播发 var broadcast = true //是否播发
// 如果有图片,添加图片信息到元数据 // 如果有图片,添加图片信息到元数据
if (hasImage) { if (hasImage) {
@ -814,10 +816,10 @@ object AgentService : CoroutineScope {
return return
} }
responseBuilder.append(token) responseBuilder.append(token)
if (speakResponse && broadcast) { if (speakResponse && reply && broadcast) {
ttsService?.speakStream(token) ttsService?.speakStream(token)
} }
if (broadcast) { if (reply && token.isNotEmpty()) {
// 发送流式回复token // 发送流式回复token
sendEvent("assistant_token", mapOf("token" to token)) sendEvent("assistant_token", mapOf("token" to token))
} }
@ -829,7 +831,7 @@ object AgentService : CoroutineScope {
override fun onComplete(sessionid:String) { override fun onComplete(sessionid:String) {
try { try {
// 视情况决定是否朗读回复 // 视情况决定是否朗读回复
if (speakResponse && broadcast && sessionid == currsessionId){ if (speakResponse && reply && broadcast && sessionid == currsessionId){
ttsService?.flushStream() ttsService?.flushStream()
audioPlayer?.stopAudio() audioPlayer?.stopAudio()
} }
@ -842,7 +844,10 @@ object AgentService : CoroutineScope {
if (hasImage) { if (hasImage) {
responseData["hasImage"] = true responseData["hasImage"] = true
} }
sendEvent("assistant_response", responseData) if (reply){
sendEvent("assistant_response", responseData)
}
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
// 添加AI回复到历史记录 // 添加AI回复到历史记录
addToHistoryMessages(createAssistantMessage(response)) addToHistoryMessages(createAssistantMessage(response))
@ -903,6 +908,7 @@ object AgentService : CoroutineScope {
val resultmap = autoHandleFcunCallResult(name, functionCallResult); val resultmap = autoHandleFcunCallResult(name, functionCallResult);
aiMetadata = resultmap["meta"] as String; aiMetadata = resultmap["meta"] as String;
broadcast = resultmap["broadcast"] as Boolean; broadcast = resultmap["broadcast"] as Boolean;
reply = !(resultmap["notreply"] as Boolean);
sendEvent( sendEvent(
"function_call_result", mapOf( "function_call_result", mapOf(
"function_call" to functionCall.toString(), "function_call" to functionCall.toString(),

144
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.LocationManager import android.location.LocationManager
import android.media.AudioManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.AlarmClock import android.provider.AlarmClock
@ -232,6 +233,42 @@ class SystemFunctionHandler(private val context: Context? = null) {
), ),
handler = ExitNavigationHandler(context) handler = ExitNavigationHandler(context)
) )
// 增加音量
client.registerLocalFunction(
name = "add_volume",
description = "增大音量",
parameters = mapOf(
"type" to "object",
"properties" to emptyMap<String, Any>(),
"required" to emptyList<String>()
),
handler = AddVolumeHandler(context)
)
// 增加音量
client.registerLocalFunction(
name = "reduce_volume",
description = "降低音量",
parameters = mapOf(
"type" to "object",
"properties" to emptyMap<String, Any>(),
"required" to emptyList<String>()
),
handler = ReduceVolumeHandler(context)
)
// 设置静音
client.registerLocalFunction(
name = "mute_volume",
description = "设置静音",
parameters = mapOf(
"type" to "object",
"properties" to emptyMap<String, Any>(),
"required" to emptyList<String>()
),
handler = MuteVolumeHandler(context)
)
} }
/** /**
@ -1320,4 +1357,109 @@ private class OpenRecorderHandler(private val context: Context?) : FunctionHandl
}.toString() }.toString()
} }
} }
} }
/**
* 添加音量处理类
*/
private class AddVolumeHandler(private val context: Context?) : FunctionHandler {
//设置音量
fun setVolume(context: Context, streamType: Int, volume: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(streamType)
val safeVolume = volume.coerceIn(0, maxVolume)
audioManager.setStreamVolume(streamType, safeVolume, AudioManager.FLAG_SHOW_UI)
}
fun getCurrentVolume(context: Context, streamType: Int): Int {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audioManager.getStreamVolume(streamType)
}
fun getMaxVolume(context: Context, streamType: Int): Int {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audioManager.getStreamMaxVolume(streamType)
}
//增加音量/减少音量
fun adjustVolume(context: Context, direction: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, AudioManager.FLAG_SHOW_UI)
}
override suspend fun handle(arguments: Map<String, Any>): String {
if (context == null) {
return JSONObject().apply {
put("error", JSONObject().apply {
put("code", "context_error")
put("message", "上下文未初始化")
})
}.toString()
}
// 发送广播通知退出交互
val result = adjustVolume(context, AudioManager.ADJUST_RAISE)
return JSONObject().apply {
put("result", "增加音量成功!")
put("broadcast", false)
}.toString()
}
}
/**
* 添加音量处理类
*/
private class ReduceVolumeHandler(private val context: Context?) : FunctionHandler {
//增加音量/减少音量
fun adjustVolume(context: Context, direction: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, AudioManager.FLAG_SHOW_UI)
}
override suspend fun handle(arguments: Map<String, Any>): String {
if (context == null) {
return JSONObject().apply {
put("error", JSONObject().apply {
put("code", "context_error")
put("message", "上下文未初始化")
})
}.toString()
}
// 发送广播通知退出交互
adjustVolume(context, AudioManager.ADJUST_LOWER)
return JSONObject().apply {
put("result", "降低音量成功!")
put("broadcast", false)
}.toString()
}
}
/**
* 添加音量处理类
*/
private class MuteVolumeHandler(private val context: Context?) : FunctionHandler {
//设置音量
fun setVolume(context: Context, streamType: Int, volume: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(streamType)
val safeVolume = volume.coerceIn(0, maxVolume)
audioManager.setStreamVolume(streamType, safeVolume, AudioManager.FLAG_SHOW_UI)
}
override suspend fun handle(arguments: Map<String, Any>): String {
if (context == null) {
return JSONObject().apply {
put("error", JSONObject().apply {
put("code", "context_error")
put("message", "上下文未初始化")
})
}.toString()
}
// 发送广播通知退出交互
setVolume(context, AudioManager.STREAM_MUSIC, 0)
return JSONObject().apply {
put("result", "设置静音成功!")
put("broadcast", false)
}.toString()
}
}

102
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt

@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat
import android.app.ActivityManager import android.app.ActivityManager
import android.app.Application import android.app.Application
import android.content.ComponentName import android.content.ComponentName
import android.media.AudioManager
import android.os.Process import android.os.Process
/** /**
* 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能 * 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能
@ -78,6 +79,13 @@ class SystemFunctionHandler(private val context: Context) {
// 注册关闭导航服务功能 // 注册关闭导航服务功能
registerStopNavigationFunction(client) registerStopNavigationFunction(client)
//增加音量
registerAddVolumeFunction(client)
//减少音量
registerReduceVolumeFunction(client)
//静音
registerMuteVolumeFunction(client)
Log.d(TAG, "所有系统功能注册完成") Log.d(TAG, "所有系统功能注册完成")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "注册系统功能失败: ${e.message}", e) Log.e(TAG, "注册系统功能失败: ${e.message}", e)
@ -438,6 +446,78 @@ class SystemFunctionHandler(private val context: Context) {
) )
Log.d(TAG, "已注册获取当前时间功能") Log.d(TAG, "已注册获取当前时间功能")
} }
/**
* 注册添加音量函数
*/
private fun registerAddVolumeFunction(client: MCPClient) {
client.registerLocalFunction(
"add_volume",
"增大音量",
JSONObject("""
{
"type": "object",
"properties": {},
"required": []
}
"""),
object : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
val result = adjustVolume(context, AudioManager.ADJUST_RAISE)
return "{\"result\": \"增加音量成功!\"}"
}
}
)
Log.d(TAG, "已注册音量调节功能")
}
/**
* 注册减少音量函数
*/
private fun registerReduceVolumeFunction(client: MCPClient) {
client.registerLocalFunction(
"reduce_volume",
"减少音量",
JSONObject("""
{
"type": "object",
"properties": {},
"required": []
}
"""),
object : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
val result = adjustVolume(context, AudioManager.ADJUST_LOWER)
return "{\"result\": \"减少音量成功!\"}"
}
}
)
Log.d(TAG, "已注册音量调节功能")
}
/**
* 注册静音函数
*/
private fun registerMuteVolumeFunction(client: MCPClient) {
client.registerLocalFunction(
"mute_volume",
"静音设置",
JSONObject("""
{
"type": "object",
"properties": {},
"required": []
}
"""),
object : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
val result = setVolume(context, AudioManager.STREAM_MUSIC, 0)
return "{\"result\": \"减少音量成功!\"}"
}
}
)
Log.d(TAG, "已注册音量调节功能")
}
/** /**
* 控制媒体播放 * 控制媒体播放
* @param action 操作类型:"play", "pause", "previous", "next" * @param action 操作类型:"play", "pause", "previous", "next"
@ -1241,6 +1321,28 @@ class SystemFunctionHandler(private val context: Context) {
} }
} }
//设置音量
fun setVolume(context: Context, streamType: Int, volume: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(streamType)
val safeVolume = volume.coerceIn(0, maxVolume)
audioManager.setStreamVolume(streamType, safeVolume, AudioManager.FLAG_SHOW_UI)
}
fun getCurrentVolume(context: Context, streamType: Int): Int {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audioManager.getStreamVolume(streamType)
}
fun getMaxVolume(context: Context, streamType: Int): Int {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audioManager.getStreamMaxVolume(streamType)
}
//增加音量/减少音量
fun adjustVolume(context: Context, direction: Int) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, AudioManager.FLAG_SHOW_UI)
}
/** /**
* 操作结果数据类 * 操作结果数据类
*/ */

Loading…
Cancel
Save