|
|
|
@ -23,9 +23,6 @@ class PermissionUtil { |
|
|
|
/// 私有构造函数 |
|
|
|
PermissionUtil._(); |
|
|
|
|
|
|
|
/// 权限解释横幅的叠加层入口 |
|
|
|
//OverlayEntry? _explanationOverlayEntry; |
|
|
|
|
|
|
|
/// 引入 get_storage 用于持久化权限拒绝次数 |
|
|
|
final _storage = GetStorage(); |
|
|
|
|
|
|
|
@ -63,7 +60,6 @@ class PermissionUtil { |
|
|
|
/// |
|
|
|
/// 返回值:`Future<bool>` 权限是否最终授予 |
|
|
|
Future<bool> requestPermission({ |
|
|
|
// required BuildContext context, |
|
|
|
required Permission permissionType, |
|
|
|
required String permissionName, |
|
|
|
required String explanationText, |
|
|
|
@ -75,145 +71,82 @@ class PermissionUtil { |
|
|
|
final String finalSettingsButtonText = |
|
|
|
settingsButtonText ?? 'goToSettings'.tr; |
|
|
|
final String finalCancelButtonText = cancelButtonText ?? 'cancel'.tr; |
|
|
|
bool isOverlayShown = false; |
|
|
|
|
|
|
|
try { |
|
|
|
// 检查初始权限状态 |
|
|
|
final PermissionStatus status = await permissionType.status; |
|
|
|
PermissionStatus status = await permissionType.status; |
|
|
|
Logger.d('PermissionUtil', '初始权限状态 [$permissionName]: $status'); |
|
|
|
|
|
|
|
// 处理已授权状态(包括授予状态和临时通知权限) |
|
|
|
if (status == PermissionStatus.granted || |
|
|
|
status == PermissionStatus.provisional) { |
|
|
|
Logger.i('PermissionUtil', '$permissionName 权限已授予或临时授权'); |
|
|
|
// 1. <--- 已授权处理 --->(包括授予状态和临时通知权限) |
|
|
|
if (status.isGranted) { |
|
|
|
Logger.i('PermissionUtil', '$permissionName 权限已授予或临时授予'); |
|
|
|
// 如果已经授权,清零之前的拒绝记录,以防用户在设置中开启后又关闭 |
|
|
|
await _storage.remove(_getDenialCountKey(permissionType)); |
|
|
|
await resetDenialCount(permissionType); // 清零拒绝记录 |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 读取历史拒绝次数 |
|
|
|
final denialCountKey = _getDenialCountKey(permissionType); |
|
|
|
int denialCount = _storage.read<int>(denialCountKey) ?? 0; |
|
|
|
// 2. <--- 熔断机制:检查我们自己的拒绝次数 ---> |
|
|
|
final denialCount = getDenialCount(permissionType); |
|
|
|
Logger.d('PermissionUtil', '$permissionName 当前拒绝次数: $denialCount'); |
|
|
|
|
|
|
|
// 3. 熔断机制:如果拒绝次数达到阈值或系统返回了 permanentlyDenied,直接视为永久拒绝 |
|
|
|
if (denialCount >= _denialThreshold || |
|
|
|
status == PermissionStatus.permanentlyDenied) { |
|
|
|
Logger.w('PermissionUtil', |
|
|
|
'$permissionName 已被拒绝 $denialCount 次或被系统永久拒绝,触发设置引导'); |
|
|
|
|
|
|
|
// 显示权限解释横幅 |
|
|
|
_showExplanationOverlay( |
|
|
|
permissionName: permissionName, |
|
|
|
explanationText: explanationText, |
|
|
|
); |
|
|
|
|
|
|
|
// 显示引导去设置的对话框 |
|
|
|
final result = await _showPermanentDenialDialog( |
|
|
|
// 如果拒绝次数达到阈值或系统返回了 permanentlyDenied,直接视为永久拒绝 |
|
|
|
if (status.isPermanentlyDenied || denialCount >= _denialThreshold) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 已被系统永久拒绝或自定义计数达到阈值($denialCount),触发设置引导'); |
|
|
|
await _showPermanentDenialDialog( |
|
|
|
permissionName: permissionName, |
|
|
|
permanentDenialText: permanentDenialText, |
|
|
|
settingsButtonText: finalSettingsButtonText, |
|
|
|
cancelButtonText: finalCancelButtonText, |
|
|
|
); |
|
|
|
|
|
|
|
// 移除解释横幅 |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return result; |
|
|
|
// 引导用户后,返回false,因为权限当前未被授予 |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
|
// 3. <--- 发起正常的权限请求 (此时 status 必然是 denied 或受限) ---> |
|
|
|
_showExplanationOverlay( |
|
|
|
//context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
explanationText: explanationText, |
|
|
|
); |
|
|
|
isOverlayShown = true; |
|
|
|
|
|
|
|
// 处理永久拒绝状态(先显示说明横幅,再引导用户去设置) |
|
|
|
// if (status == PermissionStatus.permanentlyDenied) { |
|
|
|
// Logger.w('PermissionUtil', '$permissionName 权限被永久拒绝,显示引导对话框'); |
|
|
|
|
|
|
|
// // 显示永久拒绝对话框(在解释横幅下方显示) |
|
|
|
// final result = await _showPermanentDenialDialog( |
|
|
|
// // context: context, |
|
|
|
// permissionName: permissionName, |
|
|
|
// permanentDenialText: permanentDenialText, |
|
|
|
// settingsButtonText: finalSettingsButtonText, |
|
|
|
// cancelButtonText: finalCancelButtonText, |
|
|
|
// ); |
|
|
|
// // 移除解释横幅 |
|
|
|
// _hideExplanationOverlay(); |
|
|
|
// return result; |
|
|
|
// } |
|
|
|
|
|
|
|
// 处理受限状态 |
|
|
|
if (status == PermissionStatus.restricted || |
|
|
|
status == PermissionStatus.limited) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); |
|
|
|
await _showRestrictedPermissionDialog(permissionName); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
final PermissionStatus newStatus = await permissionType.request(); |
|
|
|
Logger.d('PermissionUtil', '请求后 $permissionName 的新权限状态: $newStatus'); |
|
|
|
|
|
|
|
// 处理初始拒绝/未决定状态(真正的权限请求) |
|
|
|
if (status == PermissionStatus.denied) { |
|
|
|
Logger.d('PermissionUtil', '$permissionName 权限被拒绝或未决定,显示说明横幅并请求'); |
|
|
|
// _showExplanationOverlay( |
|
|
|
// context: context, |
|
|
|
// permissionName: permissionName, |
|
|
|
// explanationText: explanationText, |
|
|
|
// ); |
|
|
|
//请求权限 |
|
|
|
final PermissionStatus newStatus = await permissionType.request(); |
|
|
|
Logger.d('PermissionUtil', '请求后 $permissionName 的新权限状态: $newStatus'); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
isOverlayShown = false; |
|
|
|
|
|
|
|
//_hideExplanationOverlay(); |
|
|
|
// 4. <--- 处理请求后的结果 ---> |
|
|
|
if (newStatus.isGranted) { |
|
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予'); |
|
|
|
await resetDenialCount(permissionType); // 授权成功,清零 |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 5. 根据结果更新状态和拒绝次数 |
|
|
|
if (newStatus == PermissionStatus.granted || |
|
|
|
newStatus == PermissionStatus.provisional) { |
|
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予'); |
|
|
|
// 授权成功,清零拒绝次数 |
|
|
|
await _storage.remove(denialCountKey); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
if (newStatus.isPermanentlyDenied) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被系统永久拒绝'); |
|
|
|
await _showPermanentDenialDialog( |
|
|
|
permissionName: permissionName, |
|
|
|
permanentDenialText: permanentDenialText, |
|
|
|
settingsButtonText: finalSettingsButtonText, |
|
|
|
cancelButtonText: finalCancelButtonText, |
|
|
|
); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 处理请求后变为永久拒绝 |
|
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
|
final result = await _showPermanentDenialDialog( |
|
|
|
// context: context, |
|
|
|
permissionName: permissionName, |
|
|
|
permanentDenialText: permanentDenialText, |
|
|
|
settingsButtonText: finalSettingsButtonText, |
|
|
|
cancelButtonText: finalCancelButtonText, |
|
|
|
); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return result; |
|
|
|
} |
|
|
|
// 如果执行到这里,说明权限被拒绝了 (denied) |
|
|
|
else { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限被拒绝'); |
|
|
|
// 拒绝次数 +1 |
|
|
|
await _storage.write(denialCountKey, denialCount + 1); |
|
|
|
Logger.d( |
|
|
|
'PermissionUtil', '$permissionName 拒绝次数更新为: ${denialCount + 1}'); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (newStatus.isDenied) { |
|
|
|
Logger.w('PermissionUtil', '$permissionName 权限被拒绝,增加拒绝计数'); |
|
|
|
await _storage.write(_getDenialCountKey(permissionType), denialCount + 1); |
|
|
|
} |
|
|
|
|
|
|
|
// 其他未知状态处理 |
|
|
|
Logger.e('PermissionUtil', '$permissionName 处于未知状态: $status'); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
// 所有未授权的情况最终都返回 false |
|
|
|
return false; |
|
|
|
} catch (e, stackTrace) { |
|
|
|
Logger.e( |
|
|
|
'PermissionUtil', |
|
|
|
'请求 $permissionName 权限失败: $e', |
|
|
|
e, |
|
|
|
stackTrace, |
|
|
|
); |
|
|
|
_hideExplanationOverlay(); |
|
|
|
Logger.e('PermissionUtil', '请求 $permissionName 权限失败: $e', e, stackTrace); |
|
|
|
if (isOverlayShown) { |
|
|
|
_hideExplanationOverlay(); |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -236,7 +169,7 @@ class PermissionUtil { |
|
|
|
borderRadius: BorderRadius.circular(12), |
|
|
|
elevation: 8, |
|
|
|
color: Colors.grey[300], |
|
|
|
shadowColor: Colors.black.withOpacity(0.3), |
|
|
|
shadowColor: Colors.black.withValues(alpha: 0.3), |
|
|
|
child: Padding( |
|
|
|
padding: const EdgeInsets.all(16), |
|
|
|
child: Column( |
|
|
|
@ -264,64 +197,23 @@ class PermissionUtil { |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
// 防止用户通过点击背景关闭解释横幅 |
|
|
|
barrierDismissible: false, |
|
|
|
); |
|
|
|
// _hideExplanationOverlay(); // 确保移除旧叠加层 |
|
|
|
|
|
|
|
// _explanationOverlayEntry = OverlayEntry( |
|
|
|
// builder: (context) => Positioned( |
|
|
|
// top: MediaQuery.of(context).padding.top + 10, |
|
|
|
// left: 16, |
|
|
|
// right: 16, |
|
|
|
// child: Material( |
|
|
|
// borderRadius: BorderRadius.circular(12), |
|
|
|
// elevation: 8, |
|
|
|
// color: Theme.of(context).colorScheme.background, |
|
|
|
// shadowColor: Colors.black.withOpacity(0.3), |
|
|
|
// child: Padding( |
|
|
|
// padding: const EdgeInsets.all(16), |
|
|
|
// child: Column( |
|
|
|
// mainAxisSize: MainAxisSize.min, |
|
|
|
// crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
// children: [ |
|
|
|
// Text( |
|
|
|
// '需要$permissionName权限', |
|
|
|
// style: const TextStyle( |
|
|
|
// fontWeight: FontWeight.bold, |
|
|
|
// fontSize: 16, |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// const SizedBox(height: 8), |
|
|
|
// Text( |
|
|
|
// explanationText, |
|
|
|
// style: const TextStyle(fontSize: 14), |
|
|
|
// ), |
|
|
|
// ], |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ); |
|
|
|
|
|
|
|
// Future.delayed(Duration.zero, () { |
|
|
|
// if (_explanationOverlayEntry != null) { |
|
|
|
// Overlay.of(context).insert(_explanationOverlayEntry!); |
|
|
|
// Logger.i('PermissionUtil', '权限说明叠加层已显示 ($permissionName)'); |
|
|
|
// } |
|
|
|
// }); |
|
|
|
} |
|
|
|
|
|
|
|
/// 隐藏权限解释横幅 |
|
|
|
void _hideExplanationOverlay() { |
|
|
|
Get.back(); |
|
|
|
//_explanationOverlayEntry!.remove(); |
|
|
|
Logger.d('PermissionUtil', '权限说明叠加层已移除'); |
|
|
|
if (Get.isDialogOpen ?? false) { |
|
|
|
Get.back(); |
|
|
|
Logger.d('PermissionUtil', '权限说明叠加层已移除'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// 显示永久拒绝后的设置引导对话框 |
|
|
|
/// |
|
|
|
/// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限 |
|
|
|
Future<bool> _showPermanentDenialDialog({ |
|
|
|
//required BuildContext context, |
|
|
|
Future<void> _showPermanentDenialDialog({ |
|
|
|
required String permissionName, |
|
|
|
required String permanentDenialText, |
|
|
|
required String settingsButtonText, |
|
|
|
@ -355,59 +247,10 @@ class PermissionUtil { |
|
|
|
barrierDismissible: false, |
|
|
|
); |
|
|
|
|
|
|
|
// bool? userChoice = await showDialog<bool>( |
|
|
|
// context: context, |
|
|
|
// barrierDismissible: false, |
|
|
|
// builder: (context) => |
|
|
|
// ); |
|
|
|
|
|
|
|
// 用户选择前往设置 |
|
|
|
if (userChoice == true) { |
|
|
|
AppSettings.openAppSettings(); |
|
|
|
return false; // 不能立即获取权限,需要用户手动设置 |
|
|
|
} |
|
|
|
|
|
|
|
// 用户取消 |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
/// 显示受限权限对话框 |
|
|
|
/// |
|
|
|
/// 在权限受限状态(如iOS上的受限照片访问)时调用 |
|
|
|
Future<void> _showRestrictedPermissionDialog(String permissionName) async { |
|
|
|
Logger.w('PermissionUtil', '显示受限权限对话框 ($permissionName)'); |
|
|
|
Get.dialog( |
|
|
|
AlertDialog( |
|
|
|
title: Text(permissionName + 'permissionUtilRestricted'.tr), |
|
|
|
content: Text('permissionUtilRestrictedMessage'.tr), |
|
|
|
actions: [ |
|
|
|
TextButton( |
|
|
|
onPressed: () { |
|
|
|
Logger.d('PermissionUtil', '用户确认受限权限 ($permissionName)'); |
|
|
|
// Navigator.pop(context); |
|
|
|
Get.back(); |
|
|
|
}, |
|
|
|
child: Text('permissionUtilConfirm'.tr), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
// await showDialog( |
|
|
|
// context: context, |
|
|
|
// builder: (context) => AlertDialog( |
|
|
|
// title: Text('$permissionName权限受限'), |
|
|
|
// content: const Text('此功能在当前设备上不可用或受限,请检查设备设置或系统限制'), |
|
|
|
// actions: [ |
|
|
|
// TextButton( |
|
|
|
// onPressed: () { |
|
|
|
// Logger.d('PermissionUtil', '用户确认受限权限 ($permissionName)'); |
|
|
|
// Navigator.pop(context); |
|
|
|
// }, |
|
|
|
// child: const Text('确定'), |
|
|
|
// ), |
|
|
|
// ], |
|
|
|
// ), |
|
|
|
// ); |
|
|
|
} |
|
|
|
// 移除了多余的_showRestrictedPermissionDialog,因为永久拒绝流程已能覆盖 |
|
|
|
} |
|
|
|
|