From f3da9de541878b9b71755bb64216524342f197ec Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 6 Jun 2025 19:36:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor(agent):=20=E9=87=8D=E6=9E=84=E9=BA=A6?= =?UTF-8?q?=E5=85=8B=E9=A3=8E=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化麦克风权限检查流程,添加更详细的权限状态处理和用户提示。改进权限对话框的UI和交互体验,包括滑动动画和更详细的使用说明。 --- .../agent/controllers/agent_controller.dart | 164 ++++++++++++------ .../controllers/explore_controller.dart | 50 +++--- lib/modules/home/views/home_view.dart | 20 ++- 3 files changed, 144 insertions(+), 90 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index ed93f2a99..842aba4f4 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -715,54 +715,54 @@ class AgentController extends GetxController { } } - // 检查麦克风权限 +// 检查麦克风权限,返回true表示已授权,false表示未授权 Future _checkMicrophonePermission() async { try { - // iOS平台直接返回true,不处理权限问题 - if (Platform.isIOS) { + // 1.先获取当前麦克风权限状态 + final micStatus = await Permission.microphone.status; + + // 2.如果麦克风权限已授予,返回true + if (micStatus.isGranted) { return true; } - // 创建待请求权限列表 + // 3.创建待请求权限列表(这里只请求麦克风权限) List permissionsToRequest = []; - - // Android 需要考虑SDK版本不同的权限要求 permissionsToRequest.add(Permission.microphone); - // 批量请求所有需要的权限 + // 4.弹出权限说明对话框(首次拒绝/还没授权时展示) + if (micStatus.isPermanentlyDenied) { + _showPermissionDialog(true); + } else { + _showPermissionDialog(false); + } + + // 5.请求权限,并获取返回的权限状态集合 final statuses = await permissionsToRequest.request(); - // 检查是否所有权限都已授予 - bool allGranted = true; + // 6.用于记录被拒绝的权限(便于后续判断) Permission? deniedPermission; + // 7.遍历权限状态集合,检查是否有任何权限被拒绝 statuses.forEach((permission, status) { if (!status.isGranted) { - allGranted = false; deniedPermission = permission; Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status'); } }); - // 单独检查麦克风权限状态,因为这是主要需求 - final micStatus = await Permission.microphone.status; + // // 8. 如果是“永久拒绝”(用户勾选了“不再询问”) + // if (micStatus.isPermanentlyDenied) { + // _showPermissionDialog(true); + // return false; + // } - // 如果所有权限都已授予,或至少麦克风权限已授予,返回true - if (allGranted || micStatus.isGranted) { - return true; - } - - // 处理特定权限被永久拒绝的情况 - if (micStatus.isPermanentlyDenied) { - _showPermissionDialog(true); - return false; - } - - // 普通拒绝情况 - if (deniedPermission != null) { - _showPermissionDialog(false); - } + // // 9. 普通的拒绝情况,再次弹出说明对话框 + // if (deniedPermission != null) { + // _showPermissionDialog(false); + // } + // 10. 如果权限未被授予,返回 false return false; } catch (e) { Logger.e(TAG, '检查麦克风权限失败: $e'); @@ -770,6 +770,87 @@ class AgentController extends GetxController { } } + // 显示权限提示对话框 + void _showPermissionDialog(bool isPermanent) { + Get.generalDialog( + barrierDismissible: false, + barrierLabel: '权限提示', + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: Offset(0, -1), // 从上方滑入 + end: Offset(0, 0), + ).animate(CurvedAnimation(parent: anim1, curve: Curves.easeOut)), + child: child, + ); + }, + pageBuilder: (context, anim1, anim2) { + return SafeArea( + child: Align( + alignment: Alignment.topCenter, // 关键:设置为顶部对齐 + child: Padding( + padding: const EdgeInsets.only(top: 80, left: 24, right: 24), + child: Material( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(16), + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('需要麦克风权限', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + Text(isPermanent + ? '语音功能需要麦克风权限。请前往设置页面手动开启权限。' + : '语音功能需要麦克风权限。请允许使用麦克风权限以继续使用语音功能。'), + const SizedBox(height: 16), + const Text('权限使用说明:', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + const Text('• 麦克风权限:用于录制您的语音,实现语音翻译、语音助手对话等核心功能'), + const SizedBox(height: 8), + const Text('• 我们仅在您主动使用语音功能时访问麦克风'), + const SizedBox(height: 8), + const Text('• 您可以随时在系统设置中关闭权限'), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('取消'), + ), + const SizedBox(width: 8), + TextButton( + onPressed: () { + Get.back(); + if (isPermanent) { + AppSettings.openAppSettings(); + } else { + Permission.microphone.request(); + } + }, + child: Text(isPermanent ? '前往设置' : '授权'), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ); + } + // 获取Android SDK版本 Future _getAndroidSdkVersion() async { if (Platform.isAndroid) { @@ -784,35 +865,6 @@ class AgentController extends GetxController { return 0; } - // 显示权限提示对话框 - void _showPermissionDialog(bool isPermanent) { - Get.dialog( - AlertDialog( - title: const Text('需要麦克风权限'), - content: Text(isPermanent - ? '语音功能需要麦克风权限。请前往设置页面手动开启权限。' - : '语音功能需要麦克风权限。请允许使用麦克风权限以继续使用语音功能。'), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('取消'), - ), - TextButton( - onPressed: () { - Get.back(); - if (isPermanent) { - AppSettings.openAppSettings(); - } else { - Permission.microphone.request(); - } - }, - child: Text(isPermanent ? '前往设置' : '授权'), - ), - ], - ), - ); - } - // 开始语音输入 Future startVoiceInput() async { if (isListening.value) return; diff --git a/lib/modules/explore/controllers/explore_controller.dart b/lib/modules/explore/controllers/explore_controller.dart index 3d0af7ee9..1cb448ecd 100644 --- a/lib/modules/explore/controllers/explore_controller.dart +++ b/lib/modules/explore/controllers/explore_controller.dart @@ -75,28 +75,28 @@ class ExploreController extends GetxController { agentId: 'simultaneous_interpreter', targetRoute: Routes.translation, ), - AIFeature( - title: 'aiGirlfriend'.tr, // 亲子陪伴 - description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天 - icon: Icons.favorite, - startColor: const Color(0xFFE91E63), - endColor: const Color(0xFFFF5252), - iconBgColor: const Color(0xFFE91E63).withOpacity(0.2), - iconColor: const Color(0xFFE91E63), - agentId: 'cyber_girlfriend', - targetRoute: Routes.chat, - ), - AIFeature( - title: 'healthConsultant'.tr, // 健康顾问 - description: 'healthConsultantInfo'.tr, // 提供科学健康建议 - icon: Icons.healing, - startColor: const Color(0xFFFFC107), - endColor: const Color(0xFFFF9800), - iconBgColor: const Color(0xFFFFC107).withOpacity(0.2), - iconColor: const Color(0xFFFFC107), - agentId: 'health_advisor', - targetRoute: Routes.chat, - ), + // AIFeature( + // title: 'aiGirlfriend'.tr, // 亲子陪伴 + // description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天 + // icon: Icons.favorite, + // startColor: const Color(0xFFE91E63), + // endColor: const Color(0xFFFF5252), + // iconBgColor: const Color(0xFFE91E63).withOpacity(0.2), + // iconColor: const Color(0xFFE91E63), + // agentId: 'cyber_girlfriend', + // targetRoute: Routes.chat, + // ), + // AIFeature( + // title: 'healthConsultant'.tr, // 健康顾问 + // description: 'healthConsultantInfo'.tr, // 提供科学健康建议 + // icon: Icons.healing, + // startColor: const Color(0xFFFFC107), + // endColor: const Color(0xFFFF9800), + // iconBgColor: const Color(0xFFFFC107).withOpacity(0.2), + // iconColor: const Color(0xFFFFC107), + // agentId: 'health_advisor', + // targetRoute: Routes.chat, + // ), ]; // 热门功能场景列表 @@ -105,9 +105,9 @@ class ExploreController extends GetxController { title: 'conferenceAssistant'.tr, // 商务会议助手 description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译 icon: Icons.people, - startColor: const Color(0xFF3F51B5), - endColor: const Color(0xFF673AB7), - iconColor: const Color(0xFF3F51B5), + startColor: const Color(0xFFab47bc), + endColor: const Color(0xFFec407a), + iconColor: const Color(0xFFd81b60), targetRoute: Routes.meeting, ), FunctionScene( diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index 0c63e4138..be4cef42d 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -226,7 +226,6 @@ class HomeView extends GetView { // 设备信息 SizedBox(height: 12.h), Obx(() { - // 如果连接失败且有错误信息,显示错误提示 if (controller.connectionStatus == ConnectionStatus.failed && controller.connectionErrorMsg.isNotEmpty) { @@ -861,23 +860,26 @@ class HomeView extends GetView { SizedBox(height: 5.h), // 减少间距 - // 右下卡片 - AI女友 + // 右下卡片 - + // 右下卡片 - 会议助手 _buildFeatureCard( - title: 'aiCompanion'.tr, // 亲子陪伴 - description: 'aiCompanionInfo'.tr, // 懂你心意,温暖陪伴 - icon: Icons.favorite, + title: 'conferenceAssistant'.tr, // 商务会议助手 + description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译 + icon: Icons.people, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFFE91E63).withOpacity(0.3), - Color(0xFFFF5252).withOpacity(0.3), + Color(0xFFab47bc).withOpacity(0.3), // 薰衣草紫 + Color(0xFFec407a).withOpacity(0.3), // 品红 ], ), - onTap: () {}, + onTap: () { + Get.toNamed(Routes.meeting); + }, height: 85.h, // 增加高度 isDarkMode: isDarkMode, - iconColor: const Color(0xFFE91E63), + iconColor: const Color(0xFFd81b60), // 品红 ), ], ),