diff --git a/.gitignore b/.gitignore index dc6cd105c..51e5178e8 100644 --- a/.gitignore +++ b/.gitignore @@ -220,3 +220,4 @@ fastlane/screenshots/**/*.png fastlane/test_output fastlane/readme.md .trae/rules/project_rules.md +.augment/rules/imported/project_rules.md diff --git a/lib/core/themes/app_theme.dart b/lib/core/themes/app_theme.dart index 9d64fb143..b358f1f80 100644 --- a/lib/core/themes/app_theme.dart +++ b/lib/core/themes/app_theme.dart @@ -69,4 +69,4 @@ class AppTheme { ), ), ); -} \ No newline at end of file +} diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index dba6b61ba..f4a8d48d0 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -46,6 +46,26 @@ const Map enUS = { "malay": "Malay", "turkish": "Turkish", "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", @@ -68,10 +88,14 @@ const Map enUS = { "invalidPhoneFormat": "Invalid phone number format", "tip": "Tip", "pleaseEnterCompleteCode": "Please enter the complete verification code", - // 登录错误提示 "loginFailed": "Login failed, please try again", 'pleaseAcceptPrivacyPolicy': '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", @@ -116,6 +140,12 @@ const Map enUS = { "unknown": "Unknown", 'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on', '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...", @@ -127,6 +157,9 @@ const Map enUS = { "allRoundAssistant": "All-round Assistant", "allRoundAssistantInfo": "Helps solve various problems", "simultaneousTranslation": "Real-time Translation", + "faceToFaceTranslation": "Face-to-face Translation", + 'audioVideoTranslation': 'Audio/Video Translation', + 'callTranslation': 'Call Translation', "simultaneousTranslationInfo": "Instant translation for 80+ languages", "aiGirlfriend": "Parent-child bondingn", "aiGirlfriendInfo": "Intelligent company, chat at any time", @@ -165,6 +198,7 @@ const Map enUS = { 'meetingTranscript': 'Meeting Transcript', // 设置页 + "systemSettings": "System Settings", "premiumMember": "Premium Membership", "joinDevice": "Connected device:", "accountId": "Account ID:", @@ -177,6 +211,8 @@ const Map enUS = { "lastBackup": "Last backup:", "privacySettings": "Privacy Settings", "privacySettingsInfo": "Manage data collection and permissions", + "permissionManagement": "Permission Management", // 权限管理 + "permissionManagementInfo": "Manage app permission settings", // 管理应用权限设置 "logout": "Log Out", "logoutTips": "Are you sure you want to log out of the current account?", "aboutApplication": "About", @@ -204,6 +240,16 @@ const Map enUS = { 'deleteAccount': 'Delete Account', 'deleteAccountTips': '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", @@ -212,16 +258,12 @@ const Map enUS = { "imListening": "Listening...", "canStartTalking": "You may start speaking", - // 翻译页 - "selectSourceLanguage": "Select Source Language", - "selectTargetLanguage": "Select Target Language", - - //编辑个人信息页 + // 编辑个人信息页 "editProfile": "Edit Profile", "nickname": "Nickname", "save": "Save", - //国家列表 + // 国家列表 "邮箱": "Email", "中国": "China", "美国": "United States", @@ -329,51 +371,109 @@ const Map enUS = { "斐济": "Fiji", "所罗门群岛": "Solomon Islands", - // Personal Assistant Emma - "personalAssistantName": "Emma", - "personalAssistantDescription": - "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!", - "assistantTag": "Assistant", - "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!", + // 导航 + "driving": "Driving", + "walking": "Walking", + "bicycling": "Cycling", + "tsransit": "Transit", - // 同声传译 Lucy - "interpreterName": "Simultaneous Interpreter", - "interpreterDescription": - "Hi! I'm Lucy, a professional English-Chinese interpreter specializing in real-time translation for meetings, lectures, and conversations, making communication barrier-free.", - "interpreterTag": "Translation", - "interpreterWelcome": - "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!", - - // ... existing code ... - - // 亲子陪伴 芯愿 - "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!", + // 翻译模块 + // 翻译主页 + "translationHistory": "Translation History", + "enableRecording": "Enable Recording", + "viewAudioFiles": "View Audio Files", + "headsetUser": "Headset User", + "phoneUser": "Phone User", + "selectSourceLanguage": "Select Source Language", + "selectTargetLanguage": "Select Target Language", - // Translation History + // 翻译历史页 "noTranslationHistory": "No translation history", "clearHistory": "Clear History", "clearHistoryConfirm": "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", 'feedbackInfo': 'Provide feedback or suggestions', 'feedbackDescription': 'Please describe your issue or suggestion', @@ -385,7 +485,7 @@ const Map enUS = { 'feedbackSubmitSuccess': 'Feedback submitted successfully, thank you!', 'feedbackSubmitFailed': 'Failed to submit feedback, please try again later', - // Language names + // 语言名称 "中文(简体)": "Chinese (Simplified)", "中文(繁体)": "Chinese (Traditional)", "中文(香港繁体)": "Chinese (Hong Kong)", diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 5b6f8624e..610df2165 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -46,6 +46,26 @@ const Map zhCN = { "malay": "马来语", "turkish": "土耳其语", "polish": "波兰语", + "filipino": "菲律宾语", + "dutch": "荷兰语", + "swedish": "瑞典语", + "danish": "丹麦语", + "finnish": "芬兰语", + "czech": "捷克语", + "hungarian": "匈牙利语", + "norwegian": "挪威语", + "romanian": "罗马尼亚语", + "ukrainian": "乌克兰语", + "greek": "希腊语", + "bulgarian": "保加利亚语", + "croatian": "克罗地亚语", + "slovak": "斯洛伐克语", + "slovenian": "斯洛文尼亚语", + "lithuanian": "立陶宛语", + "latvian": "拉脱维亚语", + "estonian": "爱沙尼亚语", + "serbian": "塞尔维亚语", + "icelandic": "冰岛语", // 登录页 "welcome": "欢迎使用", @@ -69,6 +89,11 @@ const Map zhCN = { "pleaseEnterCompleteCode": "请输入完整的验证码", "loginFailed": "登录失败,请重试", 'pleaseAcceptPrivacyPolicy': '请先阅读并同意隐私政策和用户协议', + "enterVerificationCode": "输入验证码", + "verificationCodeSentTo": "验证码已发送至", + "resendVerificationCode": "重新发送验证码", + "login": "登录", + "usePasswordLogin": "使用密码登录", // 首页 "deviceStatus": "设备状态", @@ -112,8 +137,14 @@ const Map zhCN = { "unknown": "未知", 'bluetoothTurnedOff': '蓝牙已关闭,请打开蓝牙', 'bluetoothOff': '蓝牙已关闭', + 'leftEar': '左耳', + 'rightEar': '右耳', + 'chargingCase': '充电仓', + 'left': '左', + 'right': '右', + 'checkConnectionStatusFailed': '检查连接状态失败', - // 发现页 +// 发现页 "searchAi": "搜索AI功能...", "exploreSlogan": "智能耳机,无限可能", "exploreSlogan1": "智能陪伴,无限可能", @@ -162,6 +193,7 @@ const Map zhCN = { 'meetingTranscript': '会议原文', // 设置页 + "systemSettings": "系统设置", "premiumMember": "高级会员", "joinDevice": "已连接设备:", "accountId": "账号ID:", @@ -174,6 +206,8 @@ const Map zhCN = { "lastBackup": "上次备份:", "privacySettings": "隐私设置", "privacySettingsInfo": "管理数据收集和权限", + "permissionManagement": "权限管理", // 权限管理 + "permissionManagementInfo": "管理应用权限设置", // 管理应用权限设置 "logout": "退出登录", "logoutTips": "确定要退出当前账号吗?", "aboutApplication": "关于应用", @@ -198,6 +232,15 @@ const Map zhCN = { 'completeRecording': '完成录制', 'deleteAccount': '注销账号', 'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销', + "firmwareUpgrade": "固件升级", + "bleHeadsetFirmwareUpgrade": "BLE耳机固件版本升级", + "deviceManagement": "设备管理", + "manageConnectedDevices": "管理已绑定的蓝牙设备", + + // 设置页面 - 实时语音对话 + "realtimeVoiceChat": "实时语音对话", + "realtimeVoiceChatInfo": "体验智能语音交互功能(功能预览)", + "loading": "加载中...", // 聊天页 "clearChatHistory": "清空聊天记录", @@ -206,10 +249,6 @@ const Map zhCN = { "imListening": "正在聆听...", "canStartTalking": "你可以开始说话", - // 翻译页 - "selectSourceLanguage": "选择源语言", - "selectTargetLanguage": "选择目标语言", - //编辑个人信息页 "editProfile": "编辑个人信息", "nickname": "昵称", @@ -323,47 +362,12 @@ const Map zhCN = { "斐济": "斐济", "所罗门群岛": "所罗门群岛", - // 个人助理 Emma - "personalAssistantName": "个人助理 Emma", - "personalAssistantDescription": - "你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!", - "assistantTag": "助理", - "personalAssistantWelcome": - "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?", - "personalAssistantVoiceWelcome": "我在呢!", - // 导航 "driving": "驾车", "walking": "步行", "bicycling": "骑行", "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) "translationHistory": "翻译历史", @@ -371,6 +375,8 @@ const Map zhCN = { "viewAudioFiles": "查看录音文件", "headsetUser": "耳机用户", "phoneUser": "手机用户", + "selectSourceLanguage": "选择源语言", + "selectTargetLanguage": "选择目标语言", // 翻译历史页 (translation_history_view.dart) "noTranslationHistory": "暂无历史记录", @@ -424,12 +430,15 @@ const Map zhCN = { "skipBinding": "跳过绑定", "rescan": "重新扫描", "skip": "跳过", + "verifyingDevice": "正在验证设备...", + "confirmBinding": "确认绑定", + "associatingDevice": "正在关联设备...", + "goToBluetoothSettings": "前往蓝牙设置", // 配对状态文本 "readyToPair": "准备开始配对", "preparingPairingEnvironment": "正在准备配对环境...", "connectDeepSoundViaSystemBluetooth": "请先通过系统蓝牙连接DeapSound设备", - "verifyingDevice": "正在验证设备...", "deviceBindingConfirm": "设备绑定确认...", "establishingDeviceAssociation": "正在建立设备关联...", "waitingCompletePairing": "请稍候,正在完成配对...", diff --git a/lib/data/models/appconfig.dart b/lib/data/models/appconfig.dart index e80862486..952746321 100644 --- a/lib/data/models/appconfig.dart +++ b/lib/data/models/appconfig.dart @@ -4,7 +4,6 @@ import 'package:deep_voice/data/models/appconfig_model.dart'; class AppConfig { static late UserGetAppConfigResp config; - // 可选:提供初始化方法 static void initialize(UserGetAppConfigResp resp) { config = resp; @@ -25,4 +24,10 @@ class AppConfig { final foundAgent = config.agents.firstWhere((agent) => agent.id == agentId); return foundAgent.systemPrompt; } + + static List allproductNames() { + List names = + config.products.map((user) => user.devicename).toList(); + return names; + } } diff --git a/lib/data/models/appconfig_model.dart b/lib/data/models/appconfig_model.dart index 602abf4dd..dfbbd8c70 100644 --- a/lib/data/models/appconfig_model.dart +++ b/lib/data/models/appconfig_model.dart @@ -7,8 +7,13 @@ class UserGetAppConfigResp { final Map env; final List agents; final Map mcps; + final List products; + 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 json) => _$UserGetAppConfigRespFromJson(json); @@ -63,3 +68,20 @@ class DBMCPServer { _$DBMCPServerFromJson(json); Map 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 json) => + _$DBProductFromJson(json); + Map toJson() => _$DBProductToJson(this); +} diff --git a/lib/data/models/appconfig_model.g.dart b/lib/data/models/appconfig_model.g.dart index c4ac4cc90..3c6675929 100644 --- a/lib/data/models/appconfig_model.g.dart +++ b/lib/data/models/appconfig_model.g.dart @@ -16,6 +16,9 @@ UserGetAppConfigResp _$UserGetAppConfigRespFromJson( mcps: (json['mcps'] as Map).map( (k, e) => MapEntry(k, DBMCPServer.fromJson(e as Map)), ), + products: (json['products'] as List) + .map((e) => DBProduct.fromJson(e as Map)) + .toList(), ); Map _$UserGetAppConfigRespToJson( @@ -24,6 +27,7 @@ Map _$UserGetAppConfigRespToJson( 'env': instance.env, 'agents': instance.agents, 'mcps': instance.mcps, + 'products': instance.products, }; DBAgent _$DBAgentFromJson(Map json) => DBAgent( @@ -66,3 +70,15 @@ Map _$DBMCPServerToJson(DBMCPServer instance) => 'url': instance.url, 'tools': instance.tools, }; + +DBProduct _$DBProductFromJson(Map json) => DBProduct( + id: json['id'] as String, + factoryid: (json['factoryid'] as num).toInt(), + devicename: json['devicename'] as String, + ); + +Map _$DBProductToJson(DBProduct instance) => { + 'id': instance.id, + 'factoryid': instance.factoryid, + 'devicename': instance.devicename, + }; diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index c5be98057..3f3f9fa86 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -23,6 +23,7 @@ class User { int viplv = 0; //添加会员等级字段,0表示普通用户 String earphonename; String earphonemac; + String earphonelicense; String earphoneauthcode; // final Int64 lastbettime; // final Int64 createtime; @@ -44,6 +45,7 @@ class User { required this.viplv, // 添加默认值 required this.earphonename, required this.earphonemac, + required this.earphonelicense, required this.earphoneauthcode, // required this.lastbettime, // required this.createtime, @@ -74,6 +76,7 @@ class User { viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 earphonename: (userData?['earphonename'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '', + earphonelicense: (userData?['earphonelicense'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), // createtime: _parseTimestamp(userData['createtime']), @@ -102,6 +105,7 @@ class User { viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 earphonename: (userData?['earphonename'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '', + earphonelicense: (userData?['earphonelicense'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), // createtime: _parseTimestamp(userData['createtime']), @@ -128,6 +132,7 @@ class User { 'earphonename': earphonename, 'earphonemac': earphonemac, + "earphonelicense": earphonelicense, 'earphoneauthcode': earphoneauthcode, // 'lastbettime': lastbettime, diff --git a/lib/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index 5604838c7..e7d7187b2 100644 --- a/lib/data/services/navigation_manager.dart +++ b/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:get/get.dart'; -//音乐播放管理 +//导航播放管理 class NavigationManager extends GetxService { static NavigationManager get to => Get.find(); - // 音乐服务实例 + // 导航服务实例 final AmapService _amapervice = AmapService(); /// 初始化 diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index 0c7235c3b..c44f1b391 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -74,10 +74,19 @@ class Api { } //app获取设备信息 - static getequipmentinfo([params]) { + static getproduct([params]) { return DioManager().request( 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, ); } diff --git a/lib/data/services/network/nw_api.dart b/lib/data/services/network/nw_api.dart index c3b6a2f8b..8feff1db5 100644 --- a/lib/data/services/network/nw_api.dart +++ b/lib/data/services/network/nw_api.dart @@ -1,5 +1,5 @@ class NWApi { // static const baseApi = "https://www.deapsound.com"; - //static const baseApi = "https://api.deapsound.com"; - static const baseApi = "https://api.ideapsound.com"; + static const baseApi = "https://api.deapsound.com"; + // static const baseApi = "https://api.ideapsound.com"; } diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index e00cb158a..761d66580 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../../../routes/app_routes.dart'; import '../controllers/agent_controller.dart'; @@ -20,10 +21,10 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, controller.musicStatus(); //刷新状态 return Container( - padding: const EdgeInsets.all(14), + padding: EdgeInsets.all(14.w), decoration: BoxDecoration( color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( 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( children: [ Container( - width: 50, - height: 50, + width: 50.w, + height: 50.h, decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), + color: isDarkMode ? Colors.grey[800] : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), image: image.isNotEmpty ? DecorationImage( image: NetworkImage(image), @@ -51,12 +52,12 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, child: image.isEmpty ? Icon( Icons.music_note, - size: 18, + size: 18.sp, color: Theme.of(context).primaryColor, ) : null, ), - const SizedBox(width: 12), + SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -64,18 +65,18 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, Text( name, style: TextStyle( - fontSize: 13, + fontSize: 13.sp, fontWeight: FontWeight.w500, - color: Colors.grey[800], + color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.grey[800], ), overflow: TextOverflow.ellipsis, ), - SizedBox(height: 2), + SizedBox(height: 2.h), Text( singer, style: TextStyle( - fontSize: 12, - color: Colors.grey[600], + fontSize: 12.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], ), overflow: TextOverflow.ellipsis, ), diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index 0ad88dfe3..e5769785c 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../controllers/agent_controller.dart'; @@ -30,7 +31,15 @@ String formatDuration(String secondsStr) { return parts.join(''); } -//导航卡片 +/// 导航卡片组件 +/// +/// 各元素含义分析: +/// - [start]: 起点坐标 (经纬度格式) +/// - [end]: 终点坐标 (经纬度格式) +/// - [destination]: 目的地名称显示 +/// - [modes]: 导航方式列表,包含驾车、步行、公交等选项 +/// - [navigation]: 当前导航状态信息(可选) +/// - [height]: 卡片高度 Widget navigationCard( BuildContext context, { required String start, @@ -38,70 +47,168 @@ Widget navigationCard( required String destination, required List> modes, Map? navigation, - double height = 300, + double? height, // 改为可选参数,允许自适应高度 }) { int currentIndex = 0; - final controller = Get.find(); // 获取控制器 + final controller = Get.find(); + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Container( - height: height, + height: height?.h ?? 350.h, // 如果没有指定高度,使用350作为默认高度 decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: Colors.white, - boxShadow: const [ + borderRadius: BorderRadius.circular(16.r), + color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, + boxShadow: [ BoxShadow( - blurRadius: 8, - color: Colors.black12, - offset: Offset(0, 4), + blurRadius: 12, + color: Colors.black.withValues(alpha: isDarkMode ? 0.4 : 0.08), + offset: const Offset(0, 2), ), ], ), child: StatefulBuilder( builder: (context, setInnerState) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 路线图片 - ClipRRect( - borderRadius: - const BorderRadius.vertical(top: Radius.circular(16)), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: Image.network( - modes[currentIndex]["image"] as String, - key: ValueKey(modes[currentIndex]["ntype"] as String), - height: 160, - width: double.infinity, - fit: BoxFit.cover, + // 顶部:路线预览图片区域 + Expanded( + flex: 4, // 增加图片区域的flex值,让图片占更多空间 + child: ClipRRect( + borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), + child: Stack( + children: [ + // 路线图片 + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Image.network( + modes[currentIndex]["image"] as String, + key: ValueKey( + 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( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + // 中部:导航方式选择器 + Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(modes.length, (index) { + final mode = modes[index]; final isSelected = index == currentIndex; - return GestureDetector( - onTap: modes[index]['enable'] as bool - ? () { - setInnerState(() => currentIndex = index); - } - : null, - child: SizedBox( - width: 50, - child: Text( - (modes[index]["ntype"] as String).tr, - textAlign: TextAlign.center, - style: TextStyle( - color: modes[index]['enable'] - ? isSelected - ? Colors.blue - : Colors.black87 - : Colors.grey, - fontSize: 12, - fontWeight: FontWeight.w500, + final isEnabled = mode['enable'] as bool; + + return Expanded( + child: GestureDetector( + onTap: isEnabled + ? () { + setInnerState(() => currentIndex = index); + } + : null, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 4.w), + padding: EdgeInsets.symmetric( + vertical: 8.h, horizontal: 12.w), + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).primaryColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + 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( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // 底部:目的地信息和操作按钮 + Container( + padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h), + child: Stack( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('$destination', - style: const TextStyle( - fontSize: 12, color: Colors.black)), - Text( - '${formatDistance(modes[currentIndex]["distance"])}-约${formatDuration(modes[currentIndex]["duration"])}', - style: const TextStyle( - fontSize: 15, color: Colors.blue)), - if (navigation != null) - Text( - "导航中... ${navigation["distanceText"]}进入${navigation["nextRoad"]}", - style: TextStyle( - fontSize: 12, - color: - const Color.fromARGB(255, 164, 164, 164))) - ], + // 目的地和距离时间信息 + Padding( + padding: EdgeInsets.only(right: 70.w), // 为右下角按钮留出空间 + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 路径显示:当前位置 → 目的地 + Row( + children: [ + // 起点图标 + Icon( + Icons.my_location, + size: 16.sp, + color: Theme.of(context).primaryColor, + ), + SizedBox(width: 6.w), + // 当前位置文本 + 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, - radius: 18, - child: IconButton( - icon: const Icon(Icons.turn_left, - color: Colors.white, size: 20), - onPressed: () { - controller.openNavigation(start, end, ""); - }, + // 开始导航按钮 - 绝对定位到右下角 + Positioned( + right: 0, + bottom: 0, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + 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; + } +} diff --git a/lib/modules/agent/views/travel_card.dart b/lib/modules/agent/views/travel_card.dart index e5c01a163..be62ccda7 100644 --- a/lib/modules/agent/views/travel_card.dart +++ b/lib/modules/agent/views/travel_card.dart @@ -1,29 +1,32 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 构建旅游内容卡片 Widget buildTravelContent( BuildContext context, List> travellist) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - '以下是一些推搜索结果:', + Text( + '以下是一些推荐搜索结果:', style: TextStyle( - color: Colors.black87, - fontSize: 14, + color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87, + fontSize: 14.sp, height: 1.4, fontWeight: FontWeight.w400, ), ), - const SizedBox(height: 12), + SizedBox(height: 12.h), Container( - padding: const EdgeInsets.all(12), + padding: EdgeInsets.all(12.w), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, + borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: isDarkMode ? 0.2 : 0.03), blurRadius: 3, spreadRadius: 1, offset: const Offset(0, 1), @@ -53,16 +56,16 @@ Widget buildTravelContent( // ), // const SizedBox(height: 10), - // 动态生成音乐条目 + // 动态生成旅游景点条目 ...travellist.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; - final name = item["name"] ?? "未知歌曲"; //名字 + final name = item["name"] ?? "未知景点"; //名字 final addr = item["addr"] ?? ""; //地址 - final tag = item["tag"] ?? "未知歌手"; //标签 - final rating = item["rating"] ?? ""; //评分 - final distance = item["distance"] ?? ""; //距离 - final images = item["images"] ?? []; // 图片列表 + // final tag = item["tag"] ?? "未知标签"; //标签 + // final rating = item["rating"] ?? ""; //评分 + // final distance = item["distance"] ?? ""; //距离 + // final images = item["images"] ?? []; // 图片列表 return Column( children: [ buildLocationItem( @@ -71,7 +74,11 @@ Widget buildTravelContent( addr, ), 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(), @@ -82,19 +89,19 @@ Widget buildTravelContent( // Divider(height: 1, thickness: 1, color: Colors.grey[100]), // _buildLocationItem('浅草寺', '东京最古老的寺庙', context), - const SizedBox(height: 8), + SizedBox(height: 8.h), Center( child: TextButton( onPressed: () {}, style: TextButton.styleFrom( minimumSize: Size.zero, - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Text( '查看更多景点', style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: Theme.of(context).primaryColor, fontWeight: FontWeight.w500, ), @@ -104,41 +111,41 @@ Widget buildTravelContent( ], ), ), - const SizedBox(height: 10), + SizedBox(height: 10.h), Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( child: OutlinedButton.icon( onPressed: () {}, - icon: Icon(Icons.map, size: 16), - label: Text('查看地图'), + icon: Icon(Icons.map, size: 16.sp), + label: const Text('查看地图'), style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).primaryColor, side: BorderSide( - color: Theme.of(context).primaryColor.withOpacity(0.5)), - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + color: Theme.of(context).primaryColor.withValues(alpha: 0.5)), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), 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( child: OutlinedButton.icon( onPressed: () {}, - icon: Icon(Icons.bookmark_border, size: 16), - label: Text('保存到收藏'), + icon: Icon(Icons.bookmark_border, size: 16.sp), + label: const Text('保存到收藏'), style: OutlinedButton.styleFrom( - foregroundColor: Colors.grey[700], - side: BorderSide(color: Colors.grey[400]!), - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + foregroundColor: isDarkMode ? Colors.grey[400] : Colors.grey[700], + side: BorderSide(color: isDarkMode ? Colors.grey[600]! : Colors.grey[400]!), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), 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; return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: EdgeInsets.symmetric(vertical: 8.h), child: Row( children: [ Container( - width: 38, - height: 38, + width: 38.w, + height: 38.h, decoration: BoxDecoration( - color: isDarkMode ? Color(0xFF1A365D) : Colors.blue[50], - borderRadius: BorderRadius.circular(8), + color: isDarkMode + ? const Color(0xFF2A2A2A) + : Theme.of(context).primaryColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), ), child: Icon( Icons.place, - size: 18, - color: Color(0xFFE11D48), + size: 18.sp, + color: Theme.of(context).primaryColor, ), ), - const SizedBox(width: 12), + SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -178,19 +187,19 @@ Widget buildLocationItem( Text( name, style: TextStyle( - fontSize: 13, + fontSize: 13.sp, fontWeight: FontWeight.w500, color: isDarkMode - ? Colors.white.withOpacity(0.9) + ? Colors.white.withValues(alpha: 0.9) : Colors.grey[800], ), overflow: TextOverflow.ellipsis, ), - SizedBox(height: 2), + SizedBox(height: 2.h), Text( description, style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], ), overflow: TextOverflow.ellipsis, @@ -200,10 +209,13 @@ Widget buildLocationItem( ), IconButton( 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, - constraints: BoxConstraints(), - iconSize: 20, + constraints: const BoxConstraints(), + iconSize: 20.sp, ), ], ), diff --git a/lib/modules/agent/views/weather_card.dart b/lib/modules/agent/views/weather_card.dart index 323a4a483..8f422601b 100644 --- a/lib/modules/agent/views/weather_card.dart +++ b/lib/modules/agent/views/weather_card.dart @@ -1,5 +1,6 @@ import 'package:common_utils/common_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 构建天气内容卡片 Widget buildWeatherContent(BuildContext context, String city, @@ -12,31 +13,31 @@ Widget buildWeatherContent(BuildContext context, String city, Text( '查询天气情况:', style: TextStyle( - color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, - fontSize: 14, + color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87, + fontSize: 14.sp, height: 1.4, fontWeight: FontWeight.w400, ), ), - const SizedBox(height: 12), + SizedBox(height: 12.h), Container( - padding: const EdgeInsets.all(16), + padding: EdgeInsets.all(16.w), decoration: BoxDecoration( gradient: isDarkMode - ? LinearGradient( + ? const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)], ) - : LinearGradient( + : const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)], ), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), + color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.1), blurRadius: 8, offset: const Offset(0, 2), ), @@ -64,37 +65,37 @@ Widget buildWeatherContent(BuildContext context, String city, Text( now["dayweather"], style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.9), + fontSize: 14.sp, + color: Colors.white.withValues(alpha: 0.9), ), ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( children: [ Text( "${now["daytemp"]}°", - style: const TextStyle( - fontSize: 36, + style: TextStyle( + fontSize: 36.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), - SizedBox(width: 8), + SizedBox(width: 8.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '最高: ${now["daytemp"]}°', style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), ), ), Text( '最低: ${now["nighttemp"]}°', style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), ), ), ], @@ -105,26 +106,26 @@ Widget buildWeatherContent(BuildContext context, String city, ), Icon( getWeatherIcon(now['dayweather']), - size: 48, + size: 48.sp, color: Colors.white, ), ], ), - SizedBox(height: 16), + SizedBox(height: 16.h), Container( height: 1, - color: Colors.white.withOpacity(isDarkMode ? 0.15 : 0.2), + color: Colors.white.withValues(alpha: isDarkMode ? 0.15 : 0.2), ), - SizedBox(height: 16), - const Text( + SizedBox(height: 16.h), + Text( '未来预报', style: TextStyle( - fontSize: 14, + fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.white, ), ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate( @@ -145,35 +146,8 @@ Widget buildWeatherContent(BuildContext context, String city, }, ), ), - SizedBox(height: 16), - Container( - 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, - ), - ), - ), - ], - ), - ), + SizedBox(height: 16.h), + _buildWeatherTip(context, now, isDarkMode), ], ), ), @@ -181,6 +155,125 @@ Widget buildWeatherContent(BuildContext context, String city, ); } +/// 构建智能天气提示 +Widget _buildWeatherTip(BuildContext context, Map 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 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) { return Column( @@ -188,21 +281,21 @@ Widget _buildForecastDay(String day, IconData icon, String temp) { Text( day, style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), ), ), - SizedBox(height: 4), + SizedBox(height: 4.h), Icon( icon, - size: 20, + size: 20.sp, color: Colors.white, ), - SizedBox(height: 4), + SizedBox(height: 4.h), Text( temp, style: TextStyle( - fontSize: 12, + fontSize: 12.sp, fontWeight: FontWeight.w500, color: Colors.white, ), diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart index 2ca2e8f47..416918206 100644 --- a/lib/modules/devices/controllers/devices_controller.dart +++ b/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 { late final BleManager _bleManager; //耳机绑定 - final isearphone = false.obs; + final earphonestate = 0.obs; final earphoneName = "".obs; final earphoneMac = "".obs; + final earphoneLicense = "".obs; final earphoneCode = "".obs; //录音笔 + final voicepenstate = 0.obs; final isvoicepen = false.obs; final voicepenName = "".obs; final voicepenMac = "".obs; @@ -29,9 +31,13 @@ class DevicesController extends GetxController { _bleManager = Get.find(); earphoneName.value = User.instance.earphonename; earphoneMac.value = User.instance.earphonemac; + earphoneLicense.value = User.instance.earphonelicense; earphoneCode.value = User.instance.earphoneauthcode; 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) { User.instance.earphonename = ""; User.instance.earphonemac = ""; - User.instance.earphoneauthcode = ""; - isearphone.value = false; + User.instance.earphonelicense = ""; + earphonestate.value = 0; _bleManager.clearPairedDevices(); _bleManager.disconnect(); Get.toNamed(Routes.pairing); diff --git a/lib/modules/devices/views/devices_view.dart b/lib/modules/devices/views/devices_view.dart index d77b08253..d01198e08 100644 --- a/lib/modules/devices/views/devices_view.dart +++ b/lib/modules/devices/views/devices_view.dart @@ -93,10 +93,10 @@ class DevicesView extends GetView { () => // 耳机列表 _buildSimpleNavigationSetting( - title: controller.isearphone.value + title: controller.earphonestate.value != 0 ? controller.earphoneName.value : "未绑定", - subtitle: controller.isearphone.value + subtitle: controller.earphonestate.value != 0 ? controller.earphoneMac.value : "还未绑定设备", icon: Icons.headphones, @@ -110,7 +110,7 @@ class DevicesView extends GetView { titleColor: isDarkMode ? Colors.white : null, subtitleColor: isDarkMode ? Colors.white70 : null, isDarkMode: isDarkMode, - showbutt: controller.isearphone.value, + showbutt: controller.earphonestate == 2, onPressed: () { controller.unbindearphone(); }, diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 701291cba..5cf163904 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -129,7 +129,7 @@ class HomeController extends GetxController { update(); } catch (e) { - Logger.e(_tag, '检查连接状态失败: $e'); + Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e'); if (_isFullyConnected.value) { _isFullyConnected.value = false; _isFullyConnected.refresh(); @@ -217,7 +217,7 @@ class HomeController extends GetxController { rightStatus = deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; - return '左耳: $leftStatus / 右耳: $rightStatus'; + return '${'leftEar'.tr}: $leftStatus / ${'rightEar'.tr}: $rightStatus'; // 左耳: xxx / 右耳: xxx } String getCaseText() { @@ -232,7 +232,7 @@ class HomeController extends GetxController { caseStatus = '$caseStatus ⚡'; } - return '充电仓: $caseStatus'; + return '${'chargingCase'.tr}: $caseStatus'; // 充电仓: xxx } String getChargingText() { diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index f447a9839..c24af6607 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -109,7 +109,7 @@ class HomeView extends GetView { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'DeepSound', + 'DeapSound', style: TextStyle( fontSize: 20.sp, fontWeight: FontWeight.bold, @@ -395,7 +395,7 @@ class HomeView extends GetView { children: [ // 左耳机 _buildCompactBatteryItem( - label: '左', + label: 'left'.tr, // 左 batteryLevel: leftBattery.value, isCharging: leftEarCharging.value, isDarkMode: isDarkMode, @@ -410,7 +410,7 @@ class HomeView extends GetView { // 右耳机 _buildCompactBatteryItem( - label: '右', + label: 'right'.tr, // 右 batteryLevel: rightBattery.value, isCharging: rightEarCharging.value, isDarkMode: isDarkMode, diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index a06a2cd1f..c96439363 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -11,7 +11,6 @@ import 'weixinLogin.dart'; import '../../../data/services/network/api.dart'; import '../../../../data/models/user_Info.dart'; import '../../common/webview_page.dart'; -//import 'package:shared_preferences/shared_preferences.dart'; import 'package:get_storage/get_storage.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'dart:io'; @@ -914,10 +913,6 @@ class LoginController extends GetxController { print('getAppConfig:进来了'); // 根据API设计调整 final resp = UserGetAppConfigResp.fromJson(response); - // 存储用户信息(示例) - // _storage.write('ENV', resp.env); - // _storage.write('AGENTS', resp.agents); - // _storage.write('MCPS', resp.mcps); AppConfig.initialize(resp); return true; } catch (e) { diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart index deb838681..32f0bd7a0 100644 --- a/lib/modules/login/views/login_view.dart +++ b/lib/modules/login/views/login_view.dart @@ -236,8 +236,8 @@ class LoginView extends GetView { controller: controller.contactController, decoration: InputDecoration( hintText: controller.selectedCountryFlag == '✉️' - ? 'email'.tr // 需要在翻译文件中添加 - : 'phoneNumber'.tr, // 需要在翻译文件中添加 + ? 'email'.tr // 邮箱 + : 'phoneNumber'.tr, // 手机号码 hintStyle: TextStyle( fontSize: 14.sp, color: isDarkMode @@ -303,7 +303,7 @@ class LoginView extends GetView { style: TextStyle(fontSize: 24.sp), ), title: Text( - '${country['name']}'.tr, + '${country['name']}'.tr, // 国家名称 style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, ), @@ -584,7 +584,7 @@ class LoginView extends GetView { ), SizedBox(width: 4.w), Text( - 'expressConsent'.tr, + 'expressConsent'.tr, // 登录/注册即表示你同意 style: TextStyle( fontSize: 13.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[700], @@ -680,7 +680,7 @@ class LoginView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '输入验证码', + 'enterVerificationCode'.tr, // 输入验证码 style: TextStyle( fontSize: 20.sp, fontWeight: FontWeight.w600, @@ -689,7 +689,7 @@ class LoginView extends GetView { ), SizedBox(height: 8.h), Obx(() => Text( - '验证码已发送至 ${controller.displayContact}', + '${'verificationCodeSentTo'.tr} ${controller.displayContact}', // 验证码已发送至 style: TextStyle( fontSize: 14.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -767,8 +767,8 @@ class LoginView extends GetView { ), label: Text( controller.countdownSeconds > 0 - ? '重新发送验证码 (${controller.countdownSeconds}s)' - : '重新发送验证码', + ? '${'resendVerificationCode'.tr} (${controller.countdownSeconds}s)' // 重新发送验证码 (倒计时) + : 'resendVerificationCode'.tr, // 重新发送验证码 style: TextStyle( fontSize: 14.sp, color: controller.countdownSeconds > 0 @@ -808,7 +808,7 @@ class LoginView extends GetView { ), ) : Text( - '登录', + 'login'.tr, // 登录 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, @@ -824,7 +824,7 @@ class LoginView extends GetView { child: TextButton( onPressed: controller.usePasswordLogin, child: Text( - '使用密码登录', + 'usePasswordLogin'.tr, // 使用密码登录 style: TextStyle( fontSize: 14.sp, color: diff --git a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart index e87191f33..be56ed535 100644 --- a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart +++ b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart @@ -63,6 +63,10 @@ class OtaUpgradeController extends GetxController { final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性 final RxInt type = OtaEvent.NOT_STARTED.obs; final RxBool isUpgrade = false.obs; + + // 新增:详细状态提示 + final RxString detailedStatus = ''.obs; + final RxBool showDetailedStatus = false.obs; @override void onInit() { @@ -184,7 +188,7 @@ class OtaUpgradeController extends GetxController { jlota.init(); // API请求 - final response = await Api.getequipmentinfo({ + final response = await Api.getproduct({ "id": deviceInfo.firmwareProductId, }); @@ -271,6 +275,12 @@ class OtaUpgradeController extends GetxController { Get.back(); Future.delayed(Duration(milliseconds: 300)); + + // 检查设备电量 + if (!_checkBatteryLevel()) { + return; // 电量不足,不执行升级 + } + startDownload(); }, child: Text('更新'), @@ -283,6 +293,9 @@ class OtaUpgradeController extends GetxController { // 修改后的startDownload方法 Future startDownload() async { isDownloading.value = true; + showDetailedStatus.value = true; + detailedStatus.value = '准备下载固件包...'; + int lastIndex = updatepackageaddress.lastIndexOf('/'); String fileName = updatepackageaddress.substring(lastIndex + 1); String directoryName = "ota_upgrades"; @@ -296,6 +309,7 @@ class OtaUpgradeController extends GetxController { type.value = DOWNLOADING_UP_FILES; try { + detailedStatus.value = '正在获取下载目录...'; final Directory? downloadsDir = await getApplicationSupportDirectory(); if (downloadsDir == null) { throw FileSystemException('无法访问下载目录'); @@ -304,12 +318,14 @@ class OtaUpgradeController extends GetxController { final targetDir = Directory('${downloadsDir.path}/Download/$directoryName'); if (!await targetDir.exists()) { + detailedStatus.value = '创建下载目录...'; await targetDir.create(recursive: true); } final savePath = '${targetDir.path}/$fileName'; // 新增:检查同名文件逻辑 + detailedStatus.value = '检查本地文件...'; bool existingSameFile = false; try { var files = await targetDir.list().toList(); @@ -330,12 +346,13 @@ class OtaUpgradeController extends GetxController { if (existingSameFile) { //Get.snackbar('文件已存在', '跳过下载,直接更新'); - + detailedStatus.value = '文件已存在,准备升级...'; startUpgrading(savePath); return; } // 新增:删除其他文件逻辑 + detailedStatus.value = '清理旧文件...'; try { var files = await targetDir.list().toList(); for (var entity in files) { @@ -351,6 +368,7 @@ class OtaUpgradeController extends GetxController { print('文件保存路径: $savePath'); + detailedStatus.value = '开始下载固件包...'; final options = Options( receiveTimeout: Duration.zero, followRedirects: true, @@ -364,14 +382,19 @@ class OtaUpgradeController extends GetxController { onReceiveProgress: (received, total) { if (total != -1) { downloadProgress.value = received / total; + detailedStatus.value = '正在下载固件包... ${(received / total * 100).toStringAsFixed(1)}%'; } }, ); File file = File(savePath); if (await file.exists()) { // Get.snackbar('下载完成', '文件已保存至: $savePath'); + detailedStatus.value = '下载完成,准备升级...'; } else { Get.snackbar('错误', '文件未找到,请检查写入权限'); + detailedStatus.value = '下载失败'; + showDetailedStatus.value = false; + return; } isDownloading.value = false; startUpgrading(savePath); @@ -381,12 +404,19 @@ class OtaUpgradeController extends GetxController { } on FileSystemException catch (e) { Get.snackbar('存储失败', '无法写入文件: ${e.message}'); } catch (e) { - Get.snackbar('未知错误', e.toString()); + isDownloading.value = false; + detailedStatus.value = '下载失败,请检查网络连接'; + showDetailedStatus.value = false; + Get.snackbar('下载失败', '请检查网络连接: $e'); + print('下载失败: $e'); } } // 开始升级 void startUpgrading(String path) async { + detailedStatus.value = '准备设备连接...'; + await Future.delayed(const Duration(milliseconds: 500)); + detailedStatus.value = '正在连接设备...'; jlota.startUpgrading(address: _address, filePath: path); return; // isUpgrade.value = false; @@ -399,6 +429,60 @@ class OtaUpgradeController extends GetxController { // 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() { Ota.instance.eventStream.listen((event) { if (event.event == 'onProgress' && @@ -407,29 +491,38 @@ class OtaUpgradeController extends GetxController { progress.value = event.progress!; type.value = event.type!; isUpgrade.value = true; // 开始升级 + detailedStatus.value = '升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%'; } if (event.event == 'onStartOTA') { - type.value = OtaEvent.START_UPGRADING; + type.value = START_UPGRADING; isUpgrade.value = true; // 开始升级 + detailedStatus.value = 'OTA升级已开始...'; } if (event.event == 'onStopOTA') { - type.value = OtaEvent.UPGRADE_SUCCESS; + type.value = UPGRADE_SUCCESS; isUpgrade.value = false; + detailedStatus.value = '升级成功完成'; + showDetailedStatus.value = false; Get.snackbar('升级成功', '请退出重新连接设备'); _getCurrentVersion(); } if (event.event == 'onError') { - type.value = OtaEvent.UPGRADE_FAILED; + type.value = UPGRADE_FAILED; isUpgrade.value = false; + detailedStatus.value = '升级失败,错误码:${event.code}'; + showDetailedStatus.value = false; Get.snackbar('升级失败', '请检查设备连接或升级包,错误码:${event.code}'); } if (event.event == 'NOCONNECTION') { - type.value = OtaEvent.CONNECTION_DISCONNECTION; + type.value = CONNECTION_DISCONNECTION; isUpgrade.value = false; + detailedStatus.value = '设备连接断开'; + showDetailedStatus.value = false; } if (event.event == 'CONNECTION_SUCCESS') { - type.value = OtaEvent.CONNECTION_SUCCESS; + type.value = CONNECTION_SUCCESS; isUpgrade.value = true; // 开始升级 + detailedStatus.value = '设备连接成功'; } }); } diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart index eb4c64e3c..dd74517aa 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/ota_upgrade_controller.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../../data/services/ble_manager.dart'; class OtaUpgradeView extends GetView { const OtaUpgradeView({Key? key}) : super(key: key); @@ -89,17 +90,16 @@ class OtaUpgradeView extends GetView { ), body: SingleChildScrollView( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 设备信息卡片 - 优化后的版本 _buildDeviceCard(isDarkMode), - SizedBox(height: 24.h), + SizedBox(height: 16.h), // 更新说明 - 当有可用更新时显示 //_buildUpdateNotes(isDarkMode), - SizedBox(height: 16.h), // 状态显示区域 Obx(() { @@ -150,12 +150,15 @@ class OtaUpgradeView extends GetView { ], ), child: Padding( - padding: EdgeInsets.all(24.w), + padding: EdgeInsets.all(20.w), child: Column( children: [ // 设备图标区域 _buildDeviceIcon(isDarkMode), - SizedBox(height: 24.h), + SizedBox(height: 16.h), + // 设备详细信息区域 + _buildDeviceDetailInfo(isDarkMode), + SizedBox(height: 16.h), // 版本信息区域 _buildVersionInfo(isDarkMode), ], @@ -167,7 +170,7 @@ class OtaUpgradeView extends GetView { // 设备图标 Widget _buildDeviceIcon(bool isDarkMode) { return Container( - padding: EdgeInsets.all(20.w), + padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: isDarkMode ? Colors.white.withValues(alpha: 0.05) @@ -184,12 +187,193 @@ class OtaUpgradeView extends GetView { isDarkMode ? 'assets/images/headphone_dark.png' : 'assets/images/headphone_light.png', - height: 120.h, + height: 100.h, fit: BoxFit.contain, ), ); } + // 设备详细信息显示 + Widget _buildDeviceDetailInfo(bool isDarkMode) { + return Obx(() { + final deviceInfo = Get.find().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) { return Column( @@ -201,7 +385,7 @@ class OtaUpgradeView extends GetView { isDarkMode: isDarkMode, isLatest: false, ), - SizedBox(height: 16.h), + SizedBox(height: 8.h), // 最新版本信息 _buildVersionInfoRow( label: '最新固件版本'.tr, @@ -210,7 +394,7 @@ class OtaUpgradeView extends GetView { isLatest: true, ), // 版本状态指示器 - SizedBox(height: 20.h), + SizedBox(height: 14.h), _buildVersionStatusIndicator(isDarkMode), ], ); @@ -224,7 +408,7 @@ class OtaUpgradeView extends GetView { required bool isLatest, }) { return Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.h), decoration: BoxDecoration( color: isDarkMode ? Colors.white.withValues(alpha: 0.03) @@ -252,7 +436,8 @@ class OtaUpgradeView extends GetView { children: [ if (isLatest && controller.isUpdateAvailable.value) ...[ Container( - padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), + padding: + EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(8.r), @@ -403,7 +588,9 @@ class OtaUpgradeView extends GetView { ), child: Icon( Icons.new_releases, - color: isDarkMode ? Colors.amber[300] : Colors.amber[700], + color: isDarkMode + ? Colors.amber[300] + : Colors.amber[700], size: 18.sp, ), ), @@ -413,7 +600,9 @@ class OtaUpgradeView extends GetView { style: TextStyle( fontSize: 16.sp, 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 { ), ), style: ElevatedButton.styleFrom( - backgroundColor: isDarkMode ? const Color(0xFF0066CC) : Colors.blue, + backgroundColor: + isDarkMode ? const Color(0xFF0066CC) : Colors.blue, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 14.h), shape: RoundedRectangleBorder( @@ -597,6 +787,11 @@ class OtaUpgradeView extends GetView { return _buildProgressIndicator(isDarkMode); } + // 显示详细状态提示 + if (controller.showDetailedStatus.value) { + return _buildDetailedStatusIndicator(isDarkMode); + } + return SizedBox( width: double.infinity, child: Column( @@ -685,7 +880,9 @@ class OtaUpgradeView extends GetView { '取出双耳机,并保证电量均为30%以上的状态'.tr, style: TextStyle( fontSize: 13.sp, - color: isDarkMode ? Colors.orange[200] : Colors.orange[800], + color: isDarkMode + ? Colors.orange[200] + : Colors.orange[800], height: 1.3, ), ), @@ -700,6 +897,89 @@ class OtaUpgradeView extends GetView { }); } + // 详细状态指示器 + 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( + 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) { return Obx(() { diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 2bfcfcedb..20eb21cdf 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -431,9 +431,12 @@ class PairingController extends GetxController { try { Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})'); final result = await authbindingDevice(device); - if (result == 2) { + if (result == 10) { //验证失败 进入手动授权 await connectble(device); + } else if (result == 9) { + Logger.i(_tag, 'BLE已连接,直接完成配对'); + _transition(PairingState.completed); } else if (result == 1) { //跳转绑定界面 //验证失败 进入手动授权 @@ -557,15 +560,22 @@ class PairingController extends GetxController { //自动绑定设备 0 未合作商 1 合作商 2,已绑定 Future 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 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 Future.delayed(const Duration(seconds: 1)); // 尝试最多读取 3 次设备信息,每次间隔 1 秒 @@ -610,7 +620,7 @@ class PairingController extends GetxController { Logger.i(_tag, '绑定设备成功'); User.instance.earphonemac = currentDevice?.address ?? ''; User.instance.earphonename = currentDevice?.name ?? ''; - User.instance.earphoneauthcode = deviceCode.value; + User.instance.earphonelicense = deviceCode.value; _transition(PairingState.verifying); _verifyDevice(_currentClassicDevice!); return; @@ -642,8 +652,9 @@ class PairingController extends GetxController { void onAutoCodeInputTap() async { // 调用BLE管理器的自动绑定方法 final response = await Api.bindauthcode({ + "code": codeInputController.text.trim(), + "earphonname": currentDevice?.name, "earphonemac": currentDevice?.address, - "earphoneauthcode": codeInputController.text.trim(), }); Logger.i(_tag, '手动授权结果 ${response}'); if (response != null) { diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index 285f687a0..8b1cbb077 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -23,9 +23,7 @@ class SettingsController extends GetxController { // 语言设置 final List languageList = [ LanguageModel( - name: 'followingSystem', // 跟随系统.tr - alias: '', - value: 'system'), + name: 'followingSystem', alias: '', value: 'system'), // 跟随系统.tr LanguageModel(name: 'english', alias: 'English', value: 'en_US'), // 英语.tr LanguageModel(name: 'chinese', alias: '中文', value: 'zh_CN'), // 中文.tr LanguageModel( @@ -78,8 +76,7 @@ class SettingsController extends GetxController { LanguageModel(name: 'estonian', alias: 'Eesti', value: 'et_EE'), // 爱沙尼亚语 LanguageModel( name: 'serbian', alias: 'Srpski', value: 'sr_RS'), // 塞尔维亚语(拉丁) - LanguageModel( - name: 'icelandic', alias: 'Íslenska', value: 'is_IS'), // 新增冰岛语 + LanguageModel(name: 'icelandic', alias: 'Íslenska', value: 'is_IS'), // 冰岛语 ]; Rx language = LanguageModel().obs; diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index d8c0976b2..1977bd959 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -203,7 +203,7 @@ class SettingsView extends GetView { controller.userInfo.value.viplv] ?? 'Unknown'; return Text( - membershipString, + membershipString.tr, // 会员等级 style: TextStyle( fontSize: 10.sp, color: Colors.white, @@ -327,7 +327,7 @@ class SettingsView extends GetView { : Colors.cyan[100]!, iconColor: isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!, - value: controller.timbreName.value.tr, + value: controller.timbreName.value.tr, // 当前音色 valueStyle: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.grey[500], @@ -345,8 +345,8 @@ class SettingsView extends GetView { : Colors.grey[200]), // 实时对话入口 _buildSimpleNavigationSetting( - title: '实时语音对话', - subtitle: '体验智能语音交互功能(功能预览)', + title: 'realtimeVoiceChat'.tr, // 实时语音对话 + subtitle: 'realtimeVoiceChatInfo'.tr, // 实时语音对话信息 icon: Icons.chat_bubble_outline, iconBgColor: isDarkMode ? Colors.purple[900]!.withOpacity(0.3) @@ -412,7 +412,7 @@ class SettingsView extends GetView { Obx( () => _buildSimpleNavigationSetting( title: 'languageSetting'.tr, // 语言设置 - subtitle: controller.language.value.name.tr, + subtitle: controller.language.value.name.tr, // 当前语言 icon: Icons.language, iconBgColor: isDarkMode ? Colors.indigo[900]!.withOpacity(0.3) @@ -506,7 +506,7 @@ class SettingsView extends GetView { // 清除配对设备 _buildSimpleNavigationSetting( title: 'deviceManagement'.tr, // 设备管理 - subtitle: 'deviceManagementInfo'.tr, // 设备管理列表 + subtitle: 'manageConnectedDevices'.tr, // 管理已绑定的蓝牙设备 icon: Icons.settings_bluetooth, iconBgColor: isDarkMode ? Colors.red[900]!.withOpacity(0.3) @@ -633,8 +633,8 @@ class SettingsView extends GetView { // : Colors.grey[200]), // 测试ble耳机固件版本升级 _buildSimpleNavigationSetting( - title: '固件升级'.tr, - subtitle: 'ble耳机固件版本升级'.tr, + title: 'firmwareUpgrade'.tr, // 固件升级 + subtitle: 'bleHeadsetFirmwareUpgrade'.tr, // BLE耳机固件版本升级 icon: Icons.system_update_alt, iconBgColor: isDarkMode ? Colors.orange[900]!.withOpacity(0.3) @@ -651,14 +651,21 @@ class SettingsView extends GetView { : Colors.grey[200]), // 添加权限管理设置项 - _buildSettingItem( + _buildSimpleNavigationSetting( + title: 'permissionManagement'.tr, // 权限管理 + subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置 icon: Icons.security, - iconColor: Colors.orange, - title: '权限管理'.tr, - isDarkMode: isDarkMode, + iconBgColor: isDarkMode + ? Colors.orange[900]!.withOpacity(0.3) + : Colors.orange[100]!, + iconColor: + isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, onTap: () { Get.toNamed(Routes.permissions); }, + isDarkMode: isDarkMode, ), ], ), @@ -750,8 +757,8 @@ class SettingsView extends GetView { Obx(() => Text( // 如果版本信息为空,显示加载中 controller.appVersion.value.isEmpty - ? '${'versions'.tr} 加载中...' - : '${'versions'.tr} ${controller.appVersion.value} (Build ${controller.buildNumber.value})', + ? '${'versions'.tr} ${'loading'.tr}' // 版本 加载中 + : '${'versions'.tr} ${controller.appVersion.value} (Build ${controller.buildNumber.value})', // 版本号 style: TextStyle( fontSize: 12.sp, color: isDarkMode @@ -784,7 +791,7 @@ class SettingsView extends GetView { borderRadius: BorderRadius.circular(16.r), ), child: Text( - 'checkUpdates'.tr, + 'checkUpdates'.tr, // 检查更新 style: TextStyle( fontSize: 12.sp, color: @@ -825,7 +832,7 @@ class SettingsView extends GetView { Get.locale?.countryCode == 'CN') InkWell( onTap: () => Get.to( - () => const WebViewPage( + () => WebViewPage( url: 'https://beian.miit.gov.cn', title: 'ICP备案信息', ), @@ -1191,7 +1198,7 @@ class SettingsView extends GetView { return Container( constraints: BoxConstraints(maxWidth: 120.w), // 限制最大宽度 child: Text( - '$serviceName'.tr, + '$serviceName'.tr, // 语音服务名称 style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.grey[600], @@ -1284,7 +1291,7 @@ class SettingsView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${service['name']}'.tr, + '${service['name']}'.tr, // 服务名称 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -1296,7 +1303,7 @@ class SettingsView extends GetView { ), SizedBox(height: 4.h), Text( - '${service['description']}'.tr, + '${service['description']}'.tr, // 服务描述 style: TextStyle( fontSize: 12.sp, color: isDarkMode @@ -1421,7 +1428,7 @@ class SettingsView extends GetView { children: [ Expanded( child: Text( - language.name.tr, + language.name.tr, // 语言名称 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -1433,7 +1440,7 @@ class SettingsView extends GetView { ), ), Text( - language.alias, // 这里使用原始名称,不加 .tr + language.alias, // 语言别名(原始名称,不加.tr) style: TextStyle( fontSize: 14.sp, color: isDarkMode diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 4f12b8d40..f6451b14f 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -27,6 +27,8 @@ enum AudioOutputDevice { } class TranslationController extends GetxController { + static const String TAG = 'TranslationController'; + // 服务 final AsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = @@ -59,6 +61,14 @@ class TranslationController extends GetxController { // 主页面显示限制:只显示最近20条,保证界面性能和用户体验 static const int _maxDisplayItems = 20; + // 分页加载相关状态 + final isLoadingMore = false.obs; // 是否正在加载更多历史记录 + final hasMoreHistory = true.obs; // 是否还有更多历史记录 + final currentPage = 1.obs; // 当前页码 + final pageSize = 20; // 每页记录数量 + final isLoadingMoreError = false.obs; // 加载更多时是否出错 + bool _scrollListenerSetup = false; // 滚动监听器设置标志 + RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2 RxInt lastActiveSpeaker = 0.obs; // 上一次状态 bool isContinuePlaying = false; // 是否继续播放 @@ -181,11 +191,9 @@ class TranslationController extends GetxController { // 初始化服务 _initServices(); - suspendOtherServices(); - // 使用微任务在布局完成后自动滚动到底部 - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToBottom(); - }); + + // 🔑 reverse模式下不需要手动滚动,ListView会自然显示最新内容 + // 参考Agent模块的实现模式 } // 加载保存的语言设置 @@ -754,18 +762,28 @@ class TranslationController extends GetxController { } } - // 滚动到底部 - void _scrollToBottom() { + // 滚动到底部(适配reverse模式) + void _scrollToBottom({bool animate = true}) { if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { - scrollController.animateTo( - scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, - ); + // 🔑 reverse模式下:滚动到顶部(position = 0)来显示最新消息 + // 正常模式下:滚动到底部(maxScrollExtent)来显示最新消息 + const targetPosition = 0.0; // reverse模式下最新消息在顶部 + + if (animate) { + // 有动画的滚动,用于新消息添加时 + scrollController.animateTo( + targetPosition, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } else { + // 无动画的跳转,用于初始化时 + scrollController.jumpTo(targetPosition); + } } catch (e) { - Logger.error('滚动到底部失败: ${e.toString()}'); + Logger.error('滚动到顶部失败(reverse模式): ${e.toString()}'); } } } @@ -879,22 +897,37 @@ class TranslationController extends GetxController { // 【注意】存储中的记录都是完成态记录(isIntermediate = false) // 因为保存时已经过滤掉了中间态记录,确保数据质量 + // 初始化分页状态 + currentPage.value = 1; + isLoadingMore.value = false; + isLoadingMoreError.value = false; + // 主页面只显示最近的20条完成态记录,保证界面性能 if (allHistory.length > _maxDisplayItems) { translationHistory.value = allHistory.sublist(allHistory.length - _maxDisplayItems); + // 检查是否还有更多历史记录 + hasMoreHistory.value = true; } else { translationHistory.value = allHistory; + hasMoreHistory.value = false; } - // 使用微任务确保在视图完全渲染后滚动到底部 - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToBottom(); - }); + // 设置滚动监听器 + _setupScrollListener(); + + // 🔑 reverse模式下不需要滚动到底部,ListView会自然显示最新记录 + // 参考Agent模块:使用reverse:true时不需要手动滚动到底部 } catch (e) { _storage.remove(_historyKey); Logger.error('加载翻译历史失败: ${e.toString()}'); } + } else { + // 没有历史数据时也要初始化分页状态 + hasMoreHistory.value = false; + currentPage.value = 1; + isLoadingMore.value = false; + isLoadingMoreError.value = false; } } @@ -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 loadMoreHistory() async { + // 防止重复加载 + if (isLoadingMore.value || !hasMoreHistory.value) { + Logger.d(TAG, + '跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreHistory=${hasMoreHistory.value}'); + return; + } + + try { + isLoadingMore.value = true; + isLoadingMoreError.value = false; + + final nextPage = currentPage.value + 1; + Logger.i(TAG, '正在加载第 $nextPage 页翻译历史...'); + + // 从存储中获取所有历史记录 + final historyJson = _storage.read(_historyKey); + if (historyJson != null) { + final List historyList = jsonDecode(historyJson); + final List allHistory = + historyList.map((item) => TranslationItem.fromJson(item)).toList(); + + // 计算分页参数 + final totalItems = allHistory.length; + final totalPages = (totalItems / pageSize).ceil(); + + // 修复分页索引计算:从最新记录向前分页 + // 第1页:最新20条 (index: totalItems-20 到 totalItems) + // 第2页:接下来20条 (index: totalItems-40 到 totalItems-20) + // 第3页:再接下来20条 (index: totalItems-60 到 totalItems-40) + final endIndex = totalItems - ((nextPage - 1) * pageSize); + final startIndex = totalItems - (nextPage * pageSize); + + Logger.d(TAG, + '分页计算: 总记录=$totalItems, 总页数=$totalPages, 当前页=$nextPage, startIndex=$startIndex, endIndex=$endIndex'); + + if (endIndex > 0) { + // 获取当前页的数据,确保索引不越界 + final actualStartIndex = startIndex < 0 ? 0 : startIndex; + final actualEndIndex = endIndex > totalItems ? totalItems : endIndex; + + final pageItems = + allHistory.sublist(actualStartIndex, actualEndIndex); + Logger.d(TAG, + '实际索引: start=$actualStartIndex, end=$actualEndIndex, 获取${pageItems.length}条记录'); + + if (pageItems.isNotEmpty) { + // 🔑 简化延迟,参考Agent模块(300ms vs 1000ms) + await Future.delayed(const Duration(milliseconds: 300)); + + // 🔑 reverse模式下:将新记录插入到现有记录列表的开头 + // 由于reverse显示,用户看到的顺序不会改变,无需复杂的位置保持 + final updatedHistory = [...pageItems, ...translationHistory]; + translationHistory.assignAll(updatedHistory); + + // 更新分页状态 + currentPage.value = nextPage; + // 修复hasMoreHistory判断:当startIndex <= 0时表示已经到达最早的记录 + hasMoreHistory.value = startIndex > 0; + + Logger.i(TAG, + '成功加载 ${pageItems.length} 条历史记录,当前页: $nextPage/$totalPages, 还有更多: ${hasMoreHistory.value}'); + } else { + // 没有更多记录 + hasMoreHistory.value = false; + Logger.i(TAG, '没有更多翻译历史记录'); + } + } else { + // 索引超出范围,没有更多记录 + hasMoreHistory.value = false; + Logger.i(TAG, '已到达历史记录开头'); + } + } else { + // 没有存储数据 + hasMoreHistory.value = false; + Logger.i(TAG, '存储中没有更多历史记录'); + } + } catch (e) { + Logger.e(TAG, '加载更多历史记录失败: $e'); + isLoadingMoreError.value = true; + } finally { + isLoadingMore.value = false; + } + } + // 获取所有完成态历史记录(供历史页面使用) // 【修复】简化实现,合并存储和内存中的完成态记录 List getAllHistory() { @@ -1060,7 +1199,7 @@ class TranslationController extends GetxController { // 滚动到底部 (公共方法,用于向后兼容) void scrollToBottom({bool animate = true}) { - _scrollToBottom(); + _scrollToBottom(animate: animate); } } diff --git a/lib/modules/translation/views/translation_detail_view.dart b/lib/modules/translation/views/translation_detail_view.dart index fe0105f51..afe3e668d 100644 --- a/lib/modules/translation/views/translation_detail_view.dart +++ b/lib/modules/translation/views/translation_detail_view.dart @@ -35,9 +35,10 @@ class TranslationDetailView extends GetView { @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - + return Scaffold( - backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), + backgroundColor: + isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), appBar: _buildAppBar(isDarkMode), body: Obx(() { // 显示加载状态 @@ -48,7 +49,7 @@ class TranslationDetailView extends GetView { ), ); } - + return Column( children: [ _buildTabButtons(isDarkMode), @@ -58,11 +59,12 @@ class TranslationDetailView extends GetView { }), ); } - + // 构建应用栏 PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( - backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), + backgroundColor: + isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), elevation: 0, leading: IconButton( icon: Icon( @@ -93,7 +95,7 @@ class TranslationDetailView extends GetView { ], ); } - + // 构建选项卡按钮 Widget _buildTabButtons(bool isDarkMode) { return Padding( @@ -102,11 +104,11 @@ class TranslationDetailView extends GetView { children: [ Expanded( child: Obx(() => _buildTabButton( - title: 'originalRecord'.tr, // 原始记录 - isSelected: controller.selectedTabIndex.value == 0, - onTap: () => controller.switchTab(0), - isDarkMode: isDarkMode, - )), + title: 'originalRecord'.tr, // 原始记录 + isSelected: controller.selectedTabIndex.value == 0, + onTap: () => controller.switchTab(0), + isDarkMode: isDarkMode, + )), ), //屏蔽“记录摘要”,需要是开启 // SizedBox(width: 12.w), @@ -122,7 +124,7 @@ class TranslationDetailView extends GetView { ), ); } - + // 构建内容区域 Widget _buildContentArea(bool isDarkMode) { return Expanded( @@ -134,13 +136,13 @@ class TranslationDetailView extends GetView { children: [ _buildTimestamp(isDarkMode), SizedBox(height: 8.h), - + // 根据选中的选项卡显示不同内容 if (controller.selectedTabIndex.value == 0) ..._buildOriginalRecords(isDarkMode) else _buildSummaryWidget(isDarkMode), - + SizedBox(height: 24.h), ], ); @@ -148,7 +150,7 @@ class TranslationDetailView extends GetView { ), ); } - + // 构建时间戳 Widget _buildTimestamp(bool isDarkMode) { return Padding( @@ -164,12 +166,14 @@ class TranslationDetailView extends GetView { ), ); } - + // 构建原始记录列表 List _buildOriginalRecords(bool isDarkMode) { - return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)).toList(); + return controller.sessionItems + .map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)) + .toList(); } - + // 构建选项卡按钮 Widget _buildTabButton({ required String title, @@ -182,8 +186,8 @@ class TranslationDetailView extends GetView { child: Container( height: 40.h, decoration: BoxDecoration( - color: isSelected - ? (isDarkMode ? AppColors.primary : Colors.blue) + color: isSelected + ? (isDarkMode ? AppColors.primary : Colors.blue) : (isDarkMode ? AppColors.cardBackground : Colors.white), borderRadius: BorderRadius.circular(20.r), boxShadow: [ @@ -200,15 +204,15 @@ class TranslationDetailView extends GetView { style: TextStyle( fontSize: 15.sp, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected - ? Colors.white + color: isSelected + ? Colors.white : (isDarkMode ? Colors.white : Colors.black87), ), ), ), ); } - + // 构建会话详情中的翻译项 Widget _buildSessionItemWidget(TranslationItem item, bool isDarkMode) { final context = Get.context!; @@ -218,7 +222,8 @@ class TranslationDetailView extends GetView { decoration: BoxDecoration( color: item.isIntermediate ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 - : (item.sourceLanguageCode == controller.currentItem.sourceLanguageCode + : (item.sourceLanguageCode == + controller.currentItem.sourceLanguageCode ? Theme.of(context) .colorScheme .primary @@ -238,13 +243,35 @@ class TranslationDetailView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 源文本 - Text( - item.sourceText, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).textTheme.bodyLarge?.color, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 源文本 + Expanded( + 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) ...[ const SizedBox(height: 8), @@ -273,22 +300,6 @@ class TranslationDetailView extends GetView { Row( mainAxisSize: MainAxisSize.min, 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) ...[ SizedBox(width: 8.w), @@ -310,25 +321,25 @@ class TranslationDetailView extends GetView { ); } - // 构建总结视图 Widget _buildSummaryWidget(bool isDarkMode) { final String combinedSourceText = _getCombinedSourceText(); - + return Column( children: [ // 摘要内容容器 _buildSummaryContainer(isDarkMode), - + // 提取摘要按钮 Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)), - + // 重新生成按钮 - Obx(() => _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), + Obx(() => + _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), ], ); } - + // 构建摘要容器 Widget _buildSummaryContainer(bool isDarkMode) { return Container( @@ -354,7 +365,7 @@ class TranslationDetailView extends GetView { }), ); } - + // 构建空摘要提示 Widget _buildEmptySummaryPrompt(bool isDarkMode) { return Padding( @@ -372,7 +383,7 @@ class TranslationDetailView extends GetView { ), ); } - + // 构建摘要内容 Widget _buildSummaryContent(bool isDarkMode) { return Column( @@ -417,9 +428,12 @@ class TranslationDetailView extends GetView { Material( color: Colors.transparent, child: InkWell( - onTap: () => controller.playText(controller.summaryResult.value), + onTap: () => + controller.playText(controller.summaryResult.value), 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( padding: EdgeInsets.all(8.r), child: Icon( @@ -437,7 +451,9 @@ class TranslationDetailView extends GetView { child: InkWell( onTap: _copySummaryToClipboard, 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( padding: EdgeInsets.all(8.r), child: Icon( @@ -454,7 +470,7 @@ class TranslationDetailView extends GetView { ], ); } - + // 复制摘要到剪贴板 void _copySummaryToClipboard() { Clipboard.setData(ClipboardData(text: controller.summaryResult.value)); @@ -465,16 +481,19 @@ class TranslationDetailView extends GetView { duration: Duration(seconds: 2), ); } - + // 构建提取摘要按钮 - Widget _buildExtractSummaryButton(String combinedSourceText, bool isDarkMode) { - final bool isDisabled = controller.isGeneratingSummary.value || controller.summaryResult.value.isNotEmpty; - + Widget _buildExtractSummaryButton( + String combinedSourceText, bool isDarkMode) { + final bool isDisabled = controller.isGeneratingSummary.value || + controller.summaryResult.value.isNotEmpty; + return Container( width: double.infinity, margin: EdgeInsets.only(bottom: 16.h), child: ElevatedButton( - onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText), + onPressed: + isDisabled ? null : () => _extractSummary(combinedSourceText), style: ElevatedButton.styleFrom( backgroundColor: isDarkMode ? AppColors.primary : Colors.blue, foregroundColor: Colors.white, @@ -483,14 +502,18 @@ class TranslationDetailView extends GetView { borderRadius: BorderRadius.circular(16.r), ), elevation: 0, - disabledBackgroundColor: controller.isGeneratingSummary.value - ? isDarkMode ? AppColors.primary.withAlpha(128) : Colors.blue.withAlpha(128) + disabledBackgroundColor: controller.isGeneratingSummary.value + ? isDarkMode + ? AppColors.primary.withAlpha(128) + : Colors.blue.withAlpha(128) : Colors.grey.withAlpha(128), ), child: controller.isGeneratingSummary.value ? _buildGeneratingIndicator(isDarkMode) : Text( - controller.summaryResult.value.isEmpty ? 'extractSummary'.tr : 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要 + controller.summaryResult.value.isEmpty + ? 'extractSummary'.tr + : 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, @@ -499,7 +522,7 @@ class TranslationDetailView extends GetView { ), ); } - + // 构建生成中指示器 Widget _buildGeneratingIndicator(bool isDarkMode) { return Row( @@ -524,13 +547,15 @@ class TranslationDetailView extends GetView { ], ); } - + // 构建重新生成按钮 - Widget _buildRegenerateSummaryButton(String combinedSourceText, bool isDarkMode) { - if (controller.summaryResult.value.isEmpty || controller.isGeneratingSummary.value) { + Widget _buildRegenerateSummaryButton( + String combinedSourceText, bool isDarkMode) { + if (controller.summaryResult.value.isEmpty || + controller.isGeneratingSummary.value) { return SizedBox.shrink(); } - + return Container( width: double.infinity, margin: EdgeInsets.only(bottom: 16.h), @@ -554,17 +579,17 @@ class TranslationDetailView extends GetView { ), ); } - + // 提取摘要方法 void _extractSummary(String text) async { final result = await controller.extractSummary(text); - + // 检查是否超时,如果超时则显示重试选项 if (result.contains('超时')) { _showTimeoutDialog(text); } } - + // 显示超时对话框 void _showTimeoutDialog(String text) { Get.dialog( @@ -720,11 +745,13 @@ class TranslationDetailView extends GetView { // 分享原始记录 final String sourceText = _getCombinedSourceText(); 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 { // 分享摘要 if (controller.summaryResult.value.isNotEmpty) { - shareText = '${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要: + shareText = + '${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要: } else { Get.snackbar( 'cannotShare'.tr, // 无法分享 @@ -735,11 +762,12 @@ class TranslationDetailView extends GetView { return; } } - + // 调用系统分享 Share.share( 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)}', // 翻译会话记录 ); } -} \ No newline at end of file +} diff --git a/lib/modules/translation/views/translation_history_view.dart b/lib/modules/translation/views/translation_history_view.dart index 52b35faca..fe43db5b7 100644 --- a/lib/modules/translation/views/translation_history_view.dart +++ b/lib/modules/translation/views/translation_history_view.dart @@ -15,9 +15,10 @@ class TranslationHistoryView extends GetView { @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - + return Scaffold( - backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), + backgroundColor: + isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), appBar: _buildAppBar(isDarkMode), body: Obx(() { if (!controller.hasHistory) { @@ -46,7 +47,7 @@ class TranslationHistoryView extends GetView { itemBuilder: (context, index) { final date = controller.sortedDates[index]; final items = controller.groupedHistory.value[date]!; - + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -70,9 +71,10 @@ class TranslationHistoryView extends GetView { }), ); } - + // 构建日期组容器 - Widget _buildDateGroupContainer(List items, bool isDarkMode) { + Widget _buildDateGroupContainer( + List items, bool isDarkMode) { return Container( margin: EdgeInsets.only(bottom: 16.h), decoration: BoxDecoration( @@ -104,7 +106,7 @@ class TranslationHistoryView extends GetView { ), ); } - + // 构建组内的历史记录项 Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) { return InkWell( @@ -117,10 +119,11 @@ class TranslationHistoryView extends GetView { borderRadius: BorderRadius.circular(8.r), child: Padding( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + child: Stack( children: [ - Expanded( + // 主要内容区域 + Padding( + padding: EdgeInsets.only(right: 50.w), // 为右侧图标留出空间 child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -155,7 +158,8 @@ class TranslationHistoryView extends GetView { _formatSessionTime(item.timestamp), style: TextStyle( fontSize: 12.sp, - color: isDarkMode ? Colors.grey[500] : Colors.grey[600], + color: + isDarkMode ? Colors.grey[500] : Colors.grey[600], ), ), SizedBox(width: 12.w), @@ -165,7 +169,9 @@ class TranslationHistoryView extends GetView { _formatLanguagePair(item), style: TextStyle( fontSize: 12.sp, - color: isDarkMode ? Colors.grey[500] : Colors.grey[600], + color: isDarkMode + ? Colors.grey[500] + : Colors.grey[600], ), overflow: TextOverflow.ellipsis, ), @@ -175,37 +181,45 @@ class TranslationHistoryView extends GetView { ], ), ), - SizedBox(width: 12.w), - // 删除会话按钮 - InkWell( - onTap: () => _showDeleteSessionConfirmDialog(item.sessionId), - borderRadius: BorderRadius.circular(6.r), - splashColor: Colors.red.withOpacity(0.1), - highlightColor: Colors.red.withOpacity(0.05), - child: Container( - padding: EdgeInsets.all(8.w), - child: Icon( - Icons.delete_outline, - color: isDarkMode ? Colors.grey[500] : Colors.grey[400], - size: 18.sp, + // 右上角删除按钮 + Positioned( + top: 0, + right: -8.w, + child: InkWell( + onTap: () => _showDeleteSessionConfirmDialog(item.sessionId), + borderRadius: BorderRadius.circular(6.r), + splashColor: Colors.red.withOpacity(0.1), + highlightColor: Colors.red.withOpacity(0.05), + child: Container( + padding: EdgeInsets.all(8.w), + child: Icon( + Icons.delete_outline, + color: isDarkMode ? Colors.grey[500] : Colors.grey[400], + size: 18.sp, + ), ), ), ), - SizedBox(width: 8.w), - Icon( - Icons.chevron_right, - color: isDarkMode ? Colors.grey[600] : Colors.grey[350], - size: 20.sp, + // 右下角箭头图标 + Positioned( + bottom: 0, + right: 0.w, // 与删除图标垂直对齐 + child: Icon( + Icons.chevron_right, + color: isDarkMode ? Colors.grey[600] : Colors.grey[350], + size: 20.sp, + ), ), ], ), ), ); } - + void _showTranslationDetail(TranslationItem item) { // 获取该会话的所有翻译项 - final List sessionItems = controller.getSessionItems(item.sessionId); + final List sessionItems = + controller.getSessionItems(item.sessionId); Get.to( () => TranslationDetailView(), @@ -238,8 +252,12 @@ class TranslationHistoryView extends GetView { final languageManager = Get.find(); // 获取源语言和目标语言的中文名称 - final sourceName = languageManager.getChineseNameByAsrCode(item.sourceLanguageCode) ?? 'unknownLanguage'.tr; // 未知语言 - final targetName = languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ?? 'unknownLanguage'.tr; // 未知语言 + final sourceName = + languageManager.getChineseNameByAsrCode(item.sourceLanguageCode) ?? + 'unknownLanguage'.tr; // 未知语言 + final targetName = + languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ?? + 'unknownLanguage'.tr; // 未知语言 // 使用双向箭头符号表示翻译方向 return '$sourceName ⇄ $targetName'; @@ -355,11 +373,12 @@ class TranslationHistoryView extends GetView { ), ); } - + // 构建应用栏 PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( - backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), + backgroundColor: + isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), elevation: 0, leading: IconButton( icon: Icon( @@ -392,4 +411,4 @@ class TranslationHistoryView extends GetView { ], ); } -} \ No newline at end of file +} diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 1cd6779e3..ed9d193eb 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -109,6 +109,7 @@ class TranslationView extends GetView { )), centerTitle: true, actions: [ + Obx(() { if (controller.isRecording.value) { // 录音中:显示红色计时器样式,点击可停止录音 @@ -169,19 +170,19 @@ class TranslationView extends GetView { }, tooltip: 'viewAudioFiles'.tr, // 查看录音文件 ), - // 添加历史记录按钮 - IconButton( - icon: Icon( - Icons.history, - color: isDarkMode ? Colors.white : Colors.black, - size: 24.sp, - ), - onPressed: () async { - await controller.stopAll(); - Get.toNamed(Routes.translationHistory); - }, - tooltip: 'translationHistory'.tr, // 翻译历史 - ), + // 历史记录按钮 + // IconButton( + // icon: Icon( + // Icons.history, + // color: isDarkMode ? Colors.white : Colors.black, + // size: 24.sp, + // ), + // onPressed: () async { + // await controller.stopAll(); + // Get.toNamed(Routes.translationHistory); + // }, + // tooltip: 'translationHistory'.tr, // 翻译历史 + // ), ], ); } @@ -289,10 +290,12 @@ class TranslationView extends GetView { Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { - final itemCount = controller.translationHistory.length; + final itemCount = controller.translationHistory.length + + (controller.hasMoreHistory.value ? 1 : 0); return ListView.builder( controller: controller.scrollController, + reverse: true, // 🔑 关键优化:采用reverse模式,参考Agent模块 padding: EdgeInsets.only( left: 16.w, right: 16.w, @@ -301,8 +304,21 @@ class TranslationView extends GetView { ), itemCount: itemCount, 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 return _buildTranslationItemWidget(context, historyItem); @@ -339,13 +355,34 @@ class TranslationView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 源文本 - Text( - item.sourceText, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).textTheme.bodyLarge?.color, - ), + // 源文本和删除按钮行 + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 源文本 + 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) ...[ const SizedBox(height: 8), @@ -364,20 +401,26 @@ class TranslationView extends GetView { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodySmall?.color, + Expanded( + child: Text( + '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).textTheme.bodySmall?.color, + ), ), ), + // 播放按钮 if (!item.isIntermediate) GestureDetector( onTap: () => controller.playTranslationItem(item), - child: Icon( - Icons.volume_up, - size: 20, - color: Theme.of(context).colorScheme.primary, + child: Container( + padding: EdgeInsets.all(4.w), + child: Icon( + Icons.volume_up, + size: 20.sp, + color: Theme.of(context).colorScheme.primary, + ), ), ), ], @@ -716,4 +759,243 @@ class TranslationView extends GetView { ), ); } + + // 构建加载更多指示器 + 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( + 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), + ); + } + } } diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 01b42c684..ec5777305 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/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处理逻辑") // 如果有正在进行的AI流式输出,先停止它 + audioPlayer?.stopAudio() stopAiStream() stopTts() // 记录新的AI任务 @@ -748,6 +749,7 @@ object AgentService : CoroutineScope { val responseBuilder = StringBuilder() var aiMetadata:String = "" var userMetadata = JSONObject() + var reply = false; //是否回复 var broadcast = true //是否播发 // 如果有图片,添加图片信息到元数据 if (hasImage) { @@ -814,10 +816,10 @@ object AgentService : CoroutineScope { return } responseBuilder.append(token) - if (speakResponse && broadcast) { + if (speakResponse && reply && broadcast) { ttsService?.speakStream(token) } - if (broadcast) { + if (reply && token.isNotEmpty()) { // 发送流式回复token sendEvent("assistant_token", mapOf("token" to token)) } @@ -829,7 +831,7 @@ object AgentService : CoroutineScope { override fun onComplete(sessionid:String) { try { // 视情况决定是否朗读回复 - if (speakResponse && broadcast && sessionid == currsessionId){ + if (speakResponse && reply && broadcast && sessionid == currsessionId){ ttsService?.flushStream() audioPlayer?.stopAudio() } @@ -842,7 +844,10 @@ object AgentService : CoroutineScope { if (hasImage) { responseData["hasImage"] = true } - sendEvent("assistant_response", responseData) + if (reply){ + sendEvent("assistant_response", responseData) + } + if (response.isNotEmpty()) { // 添加AI回复到历史记录 addToHistoryMessages(createAssistantMessage(response)) @@ -903,6 +908,7 @@ object AgentService : CoroutineScope { val resultmap = autoHandleFcunCallResult(name, functionCallResult); aiMetadata = resultmap["meta"] as String; broadcast = resultmap["broadcast"] as Boolean; + reply = !(resultmap["notreply"] as Boolean); sendEvent( "function_call_result", mapOf( "function_call" to functionCall.toString(), diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt index 24f56af0d..984b48475 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt +++ b/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.pm.PackageManager import android.location.LocationManager +import android.media.AudioManager import android.net.Uri import android.os.Build import android.provider.AlarmClock @@ -232,6 +233,42 @@ class SystemFunctionHandler(private val context: Context? = null) { ), handler = ExitNavigationHandler(context) ) + + + // 增加音量 + client.registerLocalFunction( + name = "add_volume", + description = "增大音量", + parameters = mapOf( + "type" to "object", + "properties" to emptyMap(), + "required" to emptyList() + ), + handler = AddVolumeHandler(context) + ) + + // 增加音量 + client.registerLocalFunction( + name = "reduce_volume", + description = "降低音量", + parameters = mapOf( + "type" to "object", + "properties" to emptyMap(), + "required" to emptyList() + ), + handler = ReduceVolumeHandler(context) + ) + // 设置静音 + client.registerLocalFunction( + name = "mute_volume", + description = "设置静音", + parameters = mapOf( + "type" to "object", + "properties" to emptyMap(), + "required" to emptyList() + ), + handler = MuteVolumeHandler(context) + ) } /** @@ -1320,4 +1357,109 @@ private class OpenRecorderHandler(private val context: Context?) : FunctionHandl }.toString() } } -} \ No newline at end of file +} + +/** + * 添加音量处理类 + */ +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 { + 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 { + 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 { + 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() + } +} diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt index 0dbe86e5a..643b31688 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt +++ b/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.Application import android.content.ComponentName +import android.media.AudioManager import android.os.Process /** * 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能 @@ -78,6 +79,13 @@ class SystemFunctionHandler(private val context: Context) { // 注册关闭导航服务功能 registerStopNavigationFunction(client) + + //增加音量 + registerAddVolumeFunction(client) + //减少音量 + registerReduceVolumeFunction(client) + //静音 + registerMuteVolumeFunction(client) Log.d(TAG, "所有系统功能注册完成") } catch (e: Exception) { Log.e(TAG, "注册系统功能失败: ${e.message}", e) @@ -438,6 +446,78 @@ class SystemFunctionHandler(private val context: Context) { ) 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 { + 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 { + 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 { + val result = setVolume(context, AudioManager.STREAM_MUSIC, 0) + return "{\"result\": \"减少音量成功!\"}" + } + } + ) + Log.d(TAG, "已注册音量调节功能") + } + /** * 控制媒体播放 * @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) + } + /** * 操作结果数据类 */