|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; |
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
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'; |
|
|
|
|
|
|
|
|
/// 权限请求工具类 |
|
|
/// 权限请求工具类 |
|
|
/// |
|
|
/// |
|
|
@ -21,7 +22,7 @@ class PermissionUtil { |
|
|
PermissionUtil._(); |
|
|
PermissionUtil._(); |
|
|
|
|
|
|
|
|
/// 权限解释横幅的叠加层入口 |
|
|
/// 权限解释横幅的叠加层入口 |
|
|
OverlayEntry? _explanationOverlayEntry; |
|
|
//OverlayEntry? _explanationOverlayEntry; |
|
|
|
|
|
|
|
|
/// 请求指定权限 |
|
|
/// 请求指定权限 |
|
|
/// |
|
|
/// |
|
|
@ -36,7 +37,7 @@ class PermissionUtil { |
|
|
/// |
|
|
/// |
|
|
/// 返回值:`Future<bool>` 权限是否最终授予 |
|
|
/// 返回值:`Future<bool>` 权限是否最终授予 |
|
|
Future<bool> requestPermission({ |
|
|
Future<bool> requestPermission({ |
|
|
required BuildContext context, |
|
|
// required BuildContext context, |
|
|
required Permission permissionType, |
|
|
required Permission permissionType, |
|
|
required String permissionName, |
|
|
required String permissionName, |
|
|
required String explanationText, |
|
|
required String explanationText, |
|
|
@ -58,7 +59,7 @@ class PermissionUtil { |
|
|
|
|
|
|
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
// 显示权限解释横幅(覆盖所有状态的处理) |
|
|
_showExplanationOverlay( |
|
|
_showExplanationOverlay( |
|
|
context: context, |
|
|
//context: context, |
|
|
permissionName: permissionName, |
|
|
permissionName: permissionName, |
|
|
explanationText: explanationText, |
|
|
explanationText: explanationText, |
|
|
); |
|
|
); |
|
|
@ -69,7 +70,7 @@ class PermissionUtil { |
|
|
|
|
|
|
|
|
// 显示永久拒绝对话框(在解释横幅下方显示) |
|
|
// 显示永久拒绝对话框(在解释横幅下方显示) |
|
|
final result = await _showPermanentDenialDialog( |
|
|
final result = await _showPermanentDenialDialog( |
|
|
context: context, |
|
|
// context: context, |
|
|
permissionName: permissionName, |
|
|
permissionName: permissionName, |
|
|
permanentDenialText: permanentDenialText, |
|
|
permanentDenialText: permanentDenialText, |
|
|
settingsButtonText: settingsButtonText, |
|
|
settingsButtonText: settingsButtonText, |
|
|
@ -105,7 +106,7 @@ class PermissionUtil { |
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
else if (newStatus == PermissionStatus.permanentlyDenied) { |
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝'); |
|
|
final result = await _showPermanentDenialDialog( |
|
|
final result = await _showPermanentDenialDialog( |
|
|
context: context, |
|
|
// context: context, |
|
|
permissionName: permissionName, |
|
|
permissionName: permissionName, |
|
|
permanentDenialText: permanentDenialText, |
|
|
permanentDenialText: permanentDenialText, |
|
|
settingsButtonText: settingsButtonText, |
|
|
settingsButtonText: settingsButtonText, |
|
|
@ -123,7 +124,7 @@ class PermissionUtil { |
|
|
if (status == PermissionStatus.restricted || |
|
|
if (status == PermissionStatus.restricted || |
|
|
status == PermissionStatus.limited) { |
|
|
status == PermissionStatus.limited) { |
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); |
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); |
|
|
await _showRestrictedPermissionDialog(context, permissionName); |
|
|
await _showRestrictedPermissionDialog(permissionName); |
|
|
_hideExplanationOverlay(); |
|
|
_hideExplanationOverlay(); |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
@ -148,69 +149,104 @@ class PermissionUtil { |
|
|
/// |
|
|
/// |
|
|
/// 在请求权限前显示,向用户解释为什么需要这个权限 |
|
|
/// 在请求权限前显示,向用户解释为什么需要这个权限 |
|
|
void _showExplanationOverlay({ |
|
|
void _showExplanationOverlay({ |
|
|
required BuildContext context, |
|
|
|
|
|
required String permissionName, |
|
|
required String permissionName, |
|
|
required String explanationText, |
|
|
required String explanationText, |
|
|
}) { |
|
|
}) { |
|
|
_hideExplanationOverlay(); // 确保移除旧叠加层 |
|
|
Get.dialog( |
|
|
|
|
|
Stack( |
|
|
_explanationOverlayEntry = OverlayEntry( |
|
|
children: [ |
|
|
builder: (context) => Positioned( |
|
|
Positioned( |
|
|
top: MediaQuery.of(context).padding.top + 10, |
|
|
top: 10, |
|
|
left: 16, |
|
|
left: 16, |
|
|
right: 16, |
|
|
right: 16, |
|
|
child: Material( |
|
|
child: Material( |
|
|
borderRadius: BorderRadius.circular(12), |
|
|
borderRadius: BorderRadius.circular(12), |
|
|
elevation: 8, |
|
|
elevation: 8, |
|
|
color: Theme.of(context).colorScheme.background, |
|
|
color: Colors.grey[300], |
|
|
shadowColor: Colors.black.withOpacity(0.3), |
|
|
shadowColor: Colors.black.withOpacity(0.3), |
|
|
child: Padding( |
|
|
child: Padding( |
|
|
padding: const EdgeInsets.all(16), |
|
|
padding: const EdgeInsets.all(16), |
|
|
child: Column( |
|
|
child: Column( |
|
|
mainAxisSize: MainAxisSize.min, |
|
|
mainAxisSize: MainAxisSize.min, |
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
children: [ |
|
|
children: [ |
|
|
Text( |
|
|
Text( |
|
|
'需要$permissionName权限', |
|
|
'需要$permissionName权限', |
|
|
style: const TextStyle( |
|
|
style: const TextStyle( |
|
|
fontWeight: FontWeight.bold, |
|
|
fontWeight: FontWeight.bold, |
|
|
fontSize: 16, |
|
|
fontSize: 16, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
const SizedBox(height: 8), |
|
|
const SizedBox(height: 8), |
|
|
Text( |
|
|
Text( |
|
|
explanationText, |
|
|
explanationText, |
|
|
style: const TextStyle(fontSize: 14), |
|
|
style: const TextStyle(fontSize: 14), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
), |
|
|
), |
|
|
], |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
], |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
|
|
|
// _hideExplanationOverlay(); // 确保移除旧叠加层 |
|
|
|
|
|
|
|
|
Future.delayed(Duration.zero, () { |
|
|
// _explanationOverlayEntry = OverlayEntry( |
|
|
if (_explanationOverlayEntry != null) { |
|
|
// builder: (context) => Positioned( |
|
|
Overlay.of(context).insert(_explanationOverlayEntry!); |
|
|
// top: MediaQuery.of(context).padding.top + 10, |
|
|
Logger.i('PermissionUtil', '权限说明叠加层已显示 ($permissionName)'); |
|
|
// 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() { |
|
|
void _hideExplanationOverlay() { |
|
|
if (_explanationOverlayEntry != null) { |
|
|
Get.back(); |
|
|
_explanationOverlayEntry?.remove(); |
|
|
//_explanationOverlayEntry!.remove(); |
|
|
_explanationOverlayEntry = null; |
|
|
Logger.d('PermissionUtil', '权限说明叠加层已移除'); |
|
|
Logger.d('PermissionUtil', '权限说明叠加层已移除'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 显示永久拒绝后的设置引导对话框 |
|
|
/// 显示永久拒绝后的设置引导对话框 |
|
|
/// |
|
|
/// |
|
|
/// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限 |
|
|
/// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限 |
|
|
Future<bool> _showPermanentDenialDialog({ |
|
|
Future<bool> _showPermanentDenialDialog({ |
|
|
required BuildContext context, |
|
|
//required BuildContext context, |
|
|
required String permissionName, |
|
|
required String permissionName, |
|
|
required String permanentDenialText, |
|
|
required String permanentDenialText, |
|
|
required String settingsButtonText, |
|
|
required String settingsButtonText, |
|
|
@ -218,31 +254,36 @@ class PermissionUtil { |
|
|
}) async { |
|
|
}) async { |
|
|
Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)'); |
|
|
Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)'); |
|
|
|
|
|
|
|
|
bool? userChoice = await showDialog<bool>( |
|
|
bool? userChoice = await Get.dialog( |
|
|
context: context, |
|
|
AlertDialog( |
|
|
barrierDismissible: false, |
|
|
|
|
|
builder: (context) => AlertDialog( |
|
|
|
|
|
title: Text('需要$permissionName权限'), |
|
|
title: Text('需要$permissionName权限'), |
|
|
content: Text(permanentDenialText), |
|
|
content: Text(permanentDenialText), |
|
|
actions: [ |
|
|
actions: [ |
|
|
TextButton( |
|
|
TextButton( |
|
|
onPressed: () { |
|
|
onPressed: () { |
|
|
Logger.d('PermissionUtil', '用户取消设置 ($permissionName)'); |
|
|
Logger.d('PermissionUtil', '用户取消设置 ($permissionName)'); |
|
|
Navigator.pop(context, false); |
|
|
Get.back(result: false); |
|
|
}, |
|
|
}, |
|
|
child: Text(cancelButtonText), |
|
|
child: Text(cancelButtonText), |
|
|
), |
|
|
), |
|
|
ElevatedButton( |
|
|
ElevatedButton( |
|
|
onPressed: () { |
|
|
onPressed: () { |
|
|
Logger.d('PermissionUtil', '用户同意前往设置 ($permissionName)'); |
|
|
Logger.d('PermissionUtil', '用户同意前往设置 ($permissionName)'); |
|
|
Navigator.pop(context, true); |
|
|
Get.back(result: true); |
|
|
}, |
|
|
}, |
|
|
child: Text(settingsButtonText), |
|
|
child: Text(settingsButtonText), |
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
|
|
|
barrierDismissible: false, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// bool? userChoice = await showDialog<bool>( |
|
|
|
|
|
// context: context, |
|
|
|
|
|
// barrierDismissible: false, |
|
|
|
|
|
// builder: (context) => |
|
|
|
|
|
// ); |
|
|
|
|
|
|
|
|
// 用户选择前往设置 |
|
|
// 用户选择前往设置 |
|
|
if (userChoice == true) { |
|
|
if (userChoice == true) { |
|
|
AppSettings.openAppSettings(); |
|
|
AppSettings.openAppSettings(); |
|
|
@ -256,24 +297,40 @@ class PermissionUtil { |
|
|
/// 显示受限权限对话框 |
|
|
/// 显示受限权限对话框 |
|
|
/// |
|
|
/// |
|
|
/// 在权限受限状态(如iOS上的受限照片访问)时调用 |
|
|
/// 在权限受限状态(如iOS上的受限照片访问)时调用 |
|
|
Future<void> _showRestrictedPermissionDialog( |
|
|
Future<void> _showRestrictedPermissionDialog(String permissionName) async { |
|
|
BuildContext context, String permissionName) async { |
|
|
|
|
|
Logger.w('PermissionUtil', '显示受限权限对话框 ($permissionName)'); |
|
|
Logger.w('PermissionUtil', '显示受限权限对话框 ($permissionName)'); |
|
|
await showDialog( |
|
|
Get.dialog( |
|
|
context: context, |
|
|
AlertDialog( |
|
|
builder: (context) => AlertDialog( |
|
|
|
|
|
title: Text('$permissionName权限受限'), |
|
|
title: Text('$permissionName权限受限'), |
|
|
content: const Text('此功能在当前设备上不可用或受限,请检查设备设置或系统限制'), |
|
|
content: const Text('此功能在当前设备上不可用或受限,请检查设备设置或系统限制'), |
|
|
actions: [ |
|
|
actions: [ |
|
|
TextButton( |
|
|
TextButton( |
|
|
onPressed: () { |
|
|
onPressed: () { |
|
|
Logger.d('PermissionUtil', '用户确认受限权限 ($permissionName)'); |
|
|
Logger.d('PermissionUtil', '用户确认受限权限 ($permissionName)'); |
|
|
Navigator.pop(context); |
|
|
// Navigator.pop(context); |
|
|
|
|
|
Get.back(); |
|
|
}, |
|
|
}, |
|
|
child: const Text('确定'), |
|
|
child: const Text('确定'), |
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 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('确定'), |
|
|
|
|
|
// ), |
|
|
|
|
|
// ], |
|
|
|
|
|
// ), |
|
|
|
|
|
// ); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|