|
|
|
@ -3,12 +3,38 @@ import 'package:permission_handler/permission_handler.dart'; |
|
|
|
import 'package:app_settings/app_settings.dart'; |
|
|
|
import 'logger.dart'; |
|
|
|
|
|
|
|
/// 权限请求工具类 |
|
|
|
/// |
|
|
|
/// 提供统一的权限请求流程处理: |
|
|
|
/// 1. 初始权限状态检查 |
|
|
|
/// 2. 权限解释横幅显示(悬浮在UI顶部的解释性UI) |
|
|
|
/// 3. 权限请求执行 |
|
|
|
/// 4. 永久拒绝后的设置引导 |
|
|
|
/// |
|
|
|
/// 使用单例模式:`PermissionUtil.instance` |
|
|
|
|
|
|
|
class PermissionUtil { |
|
|
|
/// 单例实例 |
|
|
|
static final PermissionUtil instance = PermissionUtil._(); |
|
|
|
|
|
|
|
/// 私有构造函数 |
|
|
|
PermissionUtil._(); |
|
|
|
|
|
|
|
/// 权限解释横幅的叠加层入口 |
|
|
|
OverlayEntry? _explanationOverlayEntry; |
|
|
|
|
|
|
|
/// 请求指定权限 |
|
|
|
/// |
|
|
|
/// 参数: |
|
|
|
/// [context]: BuildContext,用于UI显示和导航 |
|
|
|
/// [permissionType]: 要请求的权限类型(Permission.microphone等) |
|
|
|
/// [permissionName]: 权限的用户友好名称(如"麦克风"、"定位") |
|
|
|
/// [explanationText]: 权限用途的详细解释文本 |
|
|
|
/// [permanentDenialText]: 永久拒绝后引导用户去设置的文案 |
|
|
|
/// [settingsButtonText]: "去设置"按钮的文本(可选,默认"前往设置") |
|
|
|
/// [cancelButtonText]: "取消"按钮的文本(可选,默认"取消") |
|
|
|
/// |
|
|
|
/// 返回值:`Future<bool>` 权限是否最终授予 |
|
|
|
Future<bool> requestPermission({ |
|
|
|
required BuildContext context, |
|
|
|
required Permission permissionType, |
|
|
|
@ -19,6 +45,7 @@ class PermissionUtil { |
|
|
|
String cancelButtonText = '取消', |
|
|
|
}) async { |
|
|
|
try { |
|
|
|
// 检查初始权限状态 |
|
|
|
final PermissionStatus status = await permissionType.status; |
|
|
|
Logger.d('PermissionUtil', '初始权限状态 [$permissionName]: $status'); |
|
|
|
|
|
|
|
@ -29,48 +56,66 @@ class PermissionUtil { |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 处理永久拒绝状态 |
|
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
|
_showExplanationOverlay( |
|
|
|
context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
explanationText: explanationText, |
|
|
|
); |
|
|
|
|
|
|
|
// 处理永久拒绝状态(先显示说明横幅,再引导用户去设置) |
|
|
|
if (status == PermissionStatus.permanentlyDenied) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限被永久拒绝,显示引导对话框'); |
|
|
|
await _showPermanentDenialDialog( |
|
|
|
|
|
|
|
// 显示永久拒绝对话框(在解释横幅下方显示) |
|
|
|
final result = await _showPermanentDenialDialog( |
|
|
|
context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
permanentDenialText: permanentDenialText, |
|
|
|
settingsButtonText: settingsButtonText, |
|
|
|
cancelButtonText: cancelButtonText, |
|
|
|
); |
|
|
|
return false; |
|
|
|
// 移除解释横幅 |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
// 处理初始拒绝/未决定状态 |
|
|
|
if (status == PermissionStatus.denied) { |
|
|
|
Logger.d('PermissionUtil', '$permissionName 权限被拒绝或未决定,显示说明横幅并请求'); |
|
|
|
_showExplanationOverlay( |
|
|
|
context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
explanationText: explanationText, |
|
|
|
); |
|
|
|
|
|
|
|
// _showExplanationOverlay( |
|
|
|
// context: context, |
|
|
|
// permissionName: permissionName, |
|
|
|
// explanationText: explanationText, |
|
|
|
// ); |
|
|
|
//请求权限 |
|
|
|
final PermissionStatus newStatus = await permissionType.request(); |
|
|
|
Logger.d('PermissionUtil', '请求后 $permissionName 的新权限状态: $newStatus'); |
|
|
|
|
|
|
|
_hideExplanationOverlay(); |
|
|
|
//_hideExplanationOverlay(); |
|
|
|
|
|
|
|
// 处理请求后的状态 |
|
|
|
if (newStatus == PermissionStatus.granted || |
|
|
|
newStatus == PermissionStatus.provisional) { |
|
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予'); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return true; |
|
|
|
} else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
|
} |
|
|
|
// 处理请求后变为永久拒绝 |
|
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
|
await _showPermanentDenialDialog( |
|
|
|
final result = await _showPermanentDenialDialog( |
|
|
|
context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
permanentDenialText: permanentDenialText, |
|
|
|
settingsButtonText: settingsButtonText, |
|
|
|
cancelButtonText: cancelButtonText, |
|
|
|
); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return result; |
|
|
|
} |
|
|
|
// 其他拒绝状态 |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
@ -79,11 +124,13 @@ class PermissionUtil { |
|
|
|
status == PermissionStatus.limited) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); |
|
|
|
await _showRestrictedPermissionDialog(context, permissionName); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 其他未知状态处理 |
|
|
|
Logger.e('PermissionUtil', '$permissionName 处于未知状态: $status'); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return false; |
|
|
|
} catch (e, stackTrace) { |
|
|
|
Logger.e( |
|
|
|
@ -114,8 +161,9 @@ class PermissionUtil { |
|
|
|
right: 16, |
|
|
|
child: Material( |
|
|
|
borderRadius: BorderRadius.circular(12), |
|
|
|
elevation: 4, |
|
|
|
elevation: 8, |
|
|
|
color: Theme.of(context).colorScheme.background, |
|
|
|
shadowColor: Colors.black.withOpacity(0.3), |
|
|
|
child: Padding( |
|
|
|
padding: const EdgeInsets.all(16), |
|
|
|
child: Column( |
|
|
|
@ -161,7 +209,7 @@ class PermissionUtil { |
|
|
|
/// 显示永久拒绝后的设置引导对话框 |
|
|
|
/// |
|
|
|
/// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限 |
|
|
|
Future<void> _showPermanentDenialDialog({ |
|
|
|
Future<bool> _showPermanentDenialDialog({ |
|
|
|
required BuildContext context, |
|
|
|
required String permissionName, |
|
|
|
required String permanentDenialText, |
|
|
|
@ -170,7 +218,7 @@ class PermissionUtil { |
|
|
|
}) async { |
|
|
|
Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)'); |
|
|
|
|
|
|
|
await showDialog<void>( |
|
|
|
bool? userChoice = await showDialog<bool>( |
|
|
|
context: context, |
|
|
|
barrierDismissible: false, |
|
|
|
builder: (context) => AlertDialog( |
|
|
|
@ -180,21 +228,29 @@ class PermissionUtil { |
|
|
|
TextButton( |
|
|
|
onPressed: () { |
|
|
|
Logger.d('PermissionUtil', '用户取消设置 ($permissionName)'); |
|
|
|
Navigator.pop(context); |
|
|
|
Navigator.pop(context, false); |
|
|
|
}, |
|
|
|
child: Text(cancelButtonText), |
|
|
|
), |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () { |
|
|
|
Logger.d('PermissionUtil', '用户同意前往设置 ($permissionName)'); |
|
|
|
Navigator.pop(context); |
|
|
|
AppSettings.openAppSettings(); |
|
|
|
Navigator.pop(context, true); |
|
|
|
}, |
|
|
|
child: Text(settingsButtonText), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
// 用户选择前往设置 |
|
|
|
if (userChoice == true) { |
|
|
|
AppSettings.openAppSettings(); |
|
|
|
return false; // 不能立即获取权限,需要用户手动设置 |
|
|
|
} |
|
|
|
|
|
|
|
// 用户取消 |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
/// 显示受限权限对话框 |
|
|
|
|