|
|
@ -3,6 +3,7 @@ import 'package:permission_handler/permission_handler.dart'; |
|
|
import 'package:app_settings/app_settings.dart'; |
|
|
import 'package:app_settings/app_settings.dart'; |
|
|
import 'logger.dart'; |
|
|
import 'logger.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
|
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
|
|
|
|
|
|
/// 权限请求工具类 |
|
|
/// 权限请求工具类 |
|
|
/// |
|
|
/// |
|
|
@ -11,6 +12,7 @@ import 'package:get/get.dart'; |
|
|
/// 2. 权限解释横幅显示(悬浮在UI顶部的解释性UI) |
|
|
/// 2. 权限解释横幅显示(悬浮在UI顶部的解释性UI) |
|
|
/// 3. 权限请求执行 |
|
|
/// 3. 权限请求执行 |
|
|
/// 4. 永久拒绝后的设置引导 |
|
|
/// 4. 永久拒绝后的设置引导 |
|
|
|
|
|
/// 5. 智能拒绝次数统计和熔断机制 |
|
|
/// |
|
|
/// |
|
|
/// 使用单例模式:`PermissionUtil.instance` |
|
|
/// 使用单例模式:`PermissionUtil.instance` |
|
|
|
|
|
|
|
|
@ -24,6 +26,30 @@ class PermissionUtil { |
|
|
/// 权限解释横幅的叠加层入口 |
|
|
/// 权限解释横幅的叠加层入口 |
|
|
//OverlayEntry? _explanationOverlayEntry; |
|
|
//OverlayEntry? _explanationOverlayEntry; |
|
|
|
|
|
|
|
|
|
|
|
/// 引入 get_storage 用于持久化权限拒绝次数 |
|
|
|
|
|
final _storage = GetStorage(); |
|
|
|
|
|
|
|
|
|
|
|
/// 权限拒绝次数阈值(达到此次数后视为永久拒绝) |
|
|
|
|
|
static const int _denialThreshold = 2; |
|
|
|
|
|
|
|
|
|
|
|
/// 获取权限拒绝次数的存储键 |
|
|
|
|
|
String _getDenialCountKey(Permission permission) { |
|
|
|
|
|
return 'permission_denial_count_${permission.toString()}'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 重置指定权限的拒绝次数 |
|
|
|
|
|
/// |
|
|
|
|
|
/// 在某些场景下可能需要手动重置拒绝次数,比如用户主动要求重新请求权限 |
|
|
|
|
|
Future<void> resetDenialCount(Permission permissionType) async { |
|
|
|
|
|
await _storage.remove(_getDenialCountKey(permissionType)); |
|
|
|
|
|
Logger.d('PermissionUtil', '已重置 ${permissionType.toString()} 的拒绝次数'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 获取指定权限的拒绝次数 |
|
|
|
|
|
int getDenialCount(Permission permissionType) { |
|
|
|
|
|
return _storage.read<int>(_getDenialCountKey(permissionType)) ?? 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/// 请求指定权限 |
|
|
/// 请求指定权限 |
|
|
/// |
|
|
/// |
|
|
/// 参数: |
|
|
/// 参数: |
|
|
@ -59,34 +85,75 @@ class PermissionUtil { |
|
|
if (status == PermissionStatus.granted || |
|
|
if (status == PermissionStatus.granted || |
|
|
status == PermissionStatus.provisional) { |
|
|
status == PermissionStatus.provisional) { |
|
|
Logger.i('PermissionUtil', '$permissionName 权限已授予或临时授权'); |
|
|
Logger.i('PermissionUtil', '$permissionName 权限已授予或临时授权'); |
|
|
|
|
|
// 如果已经授权,清零之前的拒绝记录,以防用户在设置中开启后又关闭 |
|
|
|
|
|
await _storage.remove(_getDenialCountKey(permissionType)); |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
// 2. 读取历史拒绝次数 |
|
|
_showExplanationOverlay( |
|
|
final denialCountKey = _getDenialCountKey(permissionType); |
|
|
//context: context, |
|
|
int denialCount = _storage.read<int>(denialCountKey) ?? 0; |
|
|
permissionName: permissionName, |
|
|
Logger.d('PermissionUtil', '$permissionName 当前拒绝次数: $denialCount'); |
|
|
explanationText: explanationText, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 处理永久拒绝状态(先显示说明横幅,再引导用户去设置) |
|
|
// 3. 熔断机制:如果拒绝次数达到阈值或系统返回了 permanentlyDenied,直接视为永久拒绝 |
|
|
if (status == PermissionStatus.permanentlyDenied) { |
|
|
if (denialCount >= _denialThreshold || |
|
|
Logger.w('PermissionUtil', '$permissionName 权限被永久拒绝,显示引导对话框'); |
|
|
status == PermissionStatus.permanentlyDenied) { |
|
|
|
|
|
Logger.w('PermissionUtil', |
|
|
|
|
|
'$permissionName 已被拒绝 $denialCount 次或被系统永久拒绝,触发设置引导'); |
|
|
|
|
|
|
|
|
// 显示永久拒绝对话框(在解释横幅下方显示) |
|
|
// 显示权限解释横幅 |
|
|
|
|
|
_showExplanationOverlay( |
|
|
|
|
|
permissionName: permissionName, |
|
|
|
|
|
explanationText: explanationText, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 显示引导去设置的对话框 |
|
|
final result = await _showPermanentDenialDialog( |
|
|
final result = await _showPermanentDenialDialog( |
|
|
// context: context, |
|
|
|
|
|
permissionName: permissionName, |
|
|
permissionName: permissionName, |
|
|
permanentDenialText: permanentDenialText, |
|
|
permanentDenialText: permanentDenialText, |
|
|
settingsButtonText: finalSettingsButtonText, |
|
|
settingsButtonText: finalSettingsButtonText, |
|
|
cancelButtonText: finalCancelButtonText, |
|
|
cancelButtonText: finalCancelButtonText, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
// 移除解释横幅 |
|
|
// 移除解释横幅 |
|
|
_hideExplanationOverlay(); |
|
|
_hideExplanationOverlay(); |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 处理初始拒绝/未决定状态 |
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
|
|
|
_showExplanationOverlay( |
|
|
|
|
|
//context: context, |
|
|
|
|
|
permissionName: permissionName, |
|
|
|
|
|
explanationText: explanationText, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 处理永久拒绝状态(先显示说明横幅,再引导用户去设置) |
|
|
|
|
|
// 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; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 处理初始拒绝/未决定状态(真正的权限请求) |
|
|
if (status == PermissionStatus.denied) { |
|
|
if (status == PermissionStatus.denied) { |
|
|
Logger.d('PermissionUtil', '$permissionName 权限被拒绝或未决定,显示说明横幅并请求'); |
|
|
Logger.d('PermissionUtil', '$permissionName 权限被拒绝或未决定,显示说明横幅并请求'); |
|
|
// _showExplanationOverlay( |
|
|
// _showExplanationOverlay( |
|
|
@ -94,19 +161,22 @@ class PermissionUtil { |
|
|
// permissionName: permissionName, |
|
|
// permissionName: permissionName, |
|
|
// explanationText: explanationText, |
|
|
// explanationText: explanationText, |
|
|
// ); |
|
|
// ); |
|
|
//请求权限 |
|
|
//请求权限 |
|
|
final PermissionStatus newStatus = await permissionType.request(); |
|
|
final PermissionStatus newStatus = await permissionType.request(); |
|
|
Logger.d('PermissionUtil', '请求后 $permissionName 的新权限状态: $newStatus'); |
|
|
Logger.d('PermissionUtil', '请求后 $permissionName 的新权限状态: $newStatus'); |
|
|
|
|
|
|
|
|
//_hideExplanationOverlay(); |
|
|
//_hideExplanationOverlay(); |
|
|
|
|
|
|
|
|
// 处理请求后的状态 |
|
|
// 5. 根据结果更新状态和拒绝次数 |
|
|
if (newStatus == PermissionStatus.granted || |
|
|
if (newStatus == PermissionStatus.granted || |
|
|
newStatus == PermissionStatus.provisional) { |
|
|
newStatus == PermissionStatus.provisional) { |
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予'); |
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予'); |
|
|
|
|
|
// 授权成功,清零拒绝次数 |
|
|
|
|
|
await _storage.remove(denialCountKey); |
|
|
_hideExplanationOverlay(); |
|
|
_hideExplanationOverlay(); |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 处理请求后变为永久拒绝 |
|
|
// 处理请求后变为永久拒绝 |
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
@ -120,18 +190,16 @@ class PermissionUtil { |
|
|
_hideExplanationOverlay(); |
|
|
_hideExplanationOverlay(); |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
// 其他拒绝状态 |
|
|
// 如果执行到这里,说明权限被拒绝了 (denied) |
|
|
_hideExplanationOverlay(); |
|
|
else { |
|
|
return false; |
|
|
Logger.w('PermissionUtil', '$permissionName 权限被拒绝'); |
|
|
} |
|
|
// 拒绝次数 +1 |
|
|
|
|
|
await _storage.write(denialCountKey, denialCount + 1); |
|
|
// 处理受限状态 |
|
|
Logger.d( |
|
|
if (status == PermissionStatus.restricted || |
|
|
'PermissionUtil', '$permissionName 拒绝次数更新为: ${denialCount + 1}'); |
|
|
status == PermissionStatus.limited) { |
|
|
_hideExplanationOverlay(); |
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); |
|
|
return false; |
|
|
await _showRestrictedPermissionDialog(permissionName); |
|
|
} |
|
|
_hideExplanationOverlay(); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 其他未知状态处理 |
|
|
// 其他未知状态处理 |
|
|
|