Browse Source

refactor(agent): 重构麦克风权限检查逻辑

优化麦克风权限检查流程,添加更详细的权限状态处理和用户提示。改进权限对话框的UI和交互体验,包括滑动动画和更详细的使用说明。
weicu
lxm 1 year ago
parent
commit
4b33c683d4
  1. 164
      lib/modules/agent/controllers/agent_controller.dart
  2. 50
      lib/modules/explore/controllers/explore_controller.dart
  3. 20
      lib/modules/home/views/home_view.dart

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

@ -715,54 +715,54 @@ class AgentController extends GetxController {
} }
} }
// 检查麦克风权限 // 检查麦克风权限,返回true表示已授权,false表示未授权
Future<bool> _checkMicrophonePermission() async { Future<bool> _checkMicrophonePermission() async {
try { try {
// iOS平台直接返回true,不处理权限问题 // 1.先获取当前麦克风权限状态
if (Platform.isIOS) { final micStatus = await Permission.microphone.status;
// 2.如果麦克风权限已授予,返回true
if (micStatus.isGranted) {
return true; return true;
} }
// 创建待请求权限列表 // 3.创建待请求权限列表(这里只请求麦克风权限)
List<Permission> permissionsToRequest = []; List<Permission> permissionsToRequest = [];
// Android 需要考虑SDK版本不同的权限要求
permissionsToRequest.add(Permission.microphone); permissionsToRequest.add(Permission.microphone);
// 批量请求所有需要的权限 // 4.弹出权限说明对话框(首次拒绝/还没授权时展示)
if (micStatus.isPermanentlyDenied) {
_showPermissionDialog(true);
} else {
_showPermissionDialog(false);
}
// 5.请求权限,并获取返回的权限状态集合
final statuses = await permissionsToRequest.request(); final statuses = await permissionsToRequest.request();
// 检查是否所有权限都已授予 // 6.用于记录被拒绝的权限(便于后续判断)
bool allGranted = true;
Permission? deniedPermission; Permission? deniedPermission;
// 7.遍历权限状态集合,检查是否有任何权限被拒绝
statuses.forEach((permission, status) { statuses.forEach((permission, status) {
if (!status.isGranted) { if (!status.isGranted) {
allGranted = false;
deniedPermission = permission; deniedPermission = permission;
Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status'); Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
} }
}); });
// 单独检查麦克风权限状态,因为这是主要需求 // // 8. 如果是“永久拒绝”(用户勾选了“不再询问”)
final micStatus = await Permission.microphone.status; // if (micStatus.isPermanentlyDenied) {
// _showPermissionDialog(true);
// return false;
// }
// 如果所有权限都已授予,或至少麦克风权限已授予,返回true // // 9. 普通的拒绝情况,再次弹出说明对话框
if (allGranted || micStatus.isGranted) { // if (deniedPermission != null) {
return true; // _showPermissionDialog(false);
} // }
// 处理特定权限被永久拒绝的情况
if (micStatus.isPermanentlyDenied) {
_showPermissionDialog(true);
return false;
}
// 普通拒绝情况
if (deniedPermission != null) {
_showPermissionDialog(false);
}
// 10. 如果权限未被授予,返回 false
return false; return false;
} catch (e) { } catch (e) {
Logger.e(TAG, '检查麦克风权限失败: $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<Offset>(
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版本 // 获取Android SDK版本
Future<int> _getAndroidSdkVersion() async { Future<int> _getAndroidSdkVersion() async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
@ -784,35 +865,6 @@ class AgentController extends GetxController {
return 0; 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<void> startVoiceInput() async { Future<void> startVoiceInput() async {
if (isListening.value) return; if (isListening.value) return;

50
lib/modules/explore/controllers/explore_controller.dart

@ -75,28 +75,28 @@ class ExploreController extends GetxController {
agentId: 'simultaneous_interpreter', agentId: 'simultaneous_interpreter',
targetRoute: Routes.translation, targetRoute: Routes.translation,
), ),
AIFeature( // AIFeature(
title: 'aiGirlfriend'.tr, // 亲子陪伴 // title: 'aiGirlfriend'.tr, // 亲子陪伴
description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天 // description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天
icon: Icons.favorite, // icon: Icons.favorite,
startColor: const Color(0xFFE91E63), // startColor: const Color(0xFFE91E63),
endColor: const Color(0xFFFF5252), // endColor: const Color(0xFFFF5252),
iconBgColor: const Color(0xFFE91E63).withOpacity(0.2), // iconBgColor: const Color(0xFFE91E63).withOpacity(0.2),
iconColor: const Color(0xFFE91E63), // iconColor: const Color(0xFFE91E63),
agentId: 'cyber_girlfriend', // agentId: 'cyber_girlfriend',
targetRoute: Routes.chat, // targetRoute: Routes.chat,
), // ),
AIFeature( // AIFeature(
title: 'healthConsultant'.tr, // 健康顾问 // title: 'healthConsultant'.tr, // 健康顾问
description: 'healthConsultantInfo'.tr, // 提供科学健康建议 // description: 'healthConsultantInfo'.tr, // 提供科学健康建议
icon: Icons.healing, // icon: Icons.healing,
startColor: const Color(0xFFFFC107), // startColor: const Color(0xFFFFC107),
endColor: const Color(0xFFFF9800), // endColor: const Color(0xFFFF9800),
iconBgColor: const Color(0xFFFFC107).withOpacity(0.2), // iconBgColor: const Color(0xFFFFC107).withOpacity(0.2),
iconColor: const Color(0xFFFFC107), // iconColor: const Color(0xFFFFC107),
agentId: 'health_advisor', // agentId: 'health_advisor',
targetRoute: Routes.chat, // targetRoute: Routes.chat,
), // ),
]; ];
// 热门功能场景列表 // 热门功能场景列表
@ -105,9 +105,9 @@ class ExploreController extends GetxController {
title: 'conferenceAssistant'.tr, // 商务会议助手 title: 'conferenceAssistant'.tr, // 商务会议助手
description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译 description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
icon: Icons.people, icon: Icons.people,
startColor: const Color(0xFF3F51B5), startColor: const Color(0xFFab47bc),
endColor: const Color(0xFF673AB7), endColor: const Color(0xFFec407a),
iconColor: const Color(0xFF3F51B5), iconColor: const Color(0xFFd81b60),
targetRoute: Routes.meeting, targetRoute: Routes.meeting,
), ),
FunctionScene( FunctionScene(

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

@ -226,7 +226,6 @@ class HomeView extends GetView<HomeController> {
// 设备信息 // 设备信息
SizedBox(height: 12.h), SizedBox(height: 12.h),
Obx(() { Obx(() {
// 如果连接失败且有错误信息,显示错误提示 // 如果连接失败且有错误信息,显示错误提示
if (controller.connectionStatus == ConnectionStatus.failed && if (controller.connectionStatus == ConnectionStatus.failed &&
controller.connectionErrorMsg.isNotEmpty) { controller.connectionErrorMsg.isNotEmpty) {
@ -861,23 +860,26 @@ class HomeView extends GetView<HomeController> {
SizedBox(height: 5.h), // 减少间距 SizedBox(height: 5.h), // 减少间距
// 右下卡片 - AI女友 // 右下卡片 -
// 右下卡片 - 会议助手
_buildFeatureCard( _buildFeatureCard(
title: 'aiCompanion'.tr, // 亲子陪伴 title: 'conferenceAssistant'.tr, // 商务会议助手
description: 'aiCompanionInfo'.tr, // 懂你心意,温暖陪伴 description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
icon: Icons.favorite, icon: Icons.people,
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
Color(0xFFE91E63).withOpacity(0.3), Color(0xFFab47bc).withOpacity(0.3), // 薰衣草紫
Color(0xFFFF5252).withOpacity(0.3), Color(0xFFec407a).withOpacity(0.3), // 品红
], ],
), ),
onTap: () {}, onTap: () {
Get.toNamed(Routes.meeting);
},
height: 85.h, // 增加高度 height: 85.h, // 增加高度
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
iconColor: const Color(0xFFE91E63), iconColor: const Color(0xFFd81b60), // 品红
), ),
], ],
), ),

Loading…
Cancel
Save