You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
7.5 KiB
223 lines
7.5 KiB
import 'package:flutter/material.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:app_settings/app_settings.dart';
|
|
import 'logger.dart';
|
|
|
|
class PermissionUtil {
|
|
static final PermissionUtil instance = PermissionUtil._();
|
|
PermissionUtil._();
|
|
|
|
OverlayEntry? _explanationOverlayEntry;
|
|
|
|
Future<bool> requestPermission({
|
|
required BuildContext context,
|
|
required Permission permissionType,
|
|
required String permissionName,
|
|
required String explanationText,
|
|
required String permanentDenialText,
|
|
String settingsButtonText = '前往设置',
|
|
String cancelButtonText = '取消',
|
|
}) async {
|
|
try {
|
|
final PermissionStatus status = await permissionType.status;
|
|
Logger.d('PermissionUtil', '初始权限状态 [$permissionName]: $status');
|
|
|
|
// 处理已授权状态(包括授予状态和临时通知权限)
|
|
if (status == PermissionStatus.granted ||
|
|
status == PermissionStatus.provisional) {
|
|
Logger.i('PermissionUtil', '$permissionName 权限已授予或临时授权');
|
|
return true;
|
|
}
|
|
|
|
// 处理永久拒绝状态
|
|
if (status == PermissionStatus.permanentlyDenied) {
|
|
Logger.w('PermissionUtil', '$permissionName 权限被永久拒绝,显示引导对话框');
|
|
await _showPermanentDenialDialog(
|
|
context: context,
|
|
permissionName: permissionName,
|
|
permanentDenialText: permanentDenialText,
|
|
settingsButtonText: settingsButtonText,
|
|
cancelButtonText: cancelButtonText,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 处理初始拒绝/未决定状态
|
|
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();
|
|
|
|
// 处理请求后的状态
|
|
if (newStatus == PermissionStatus.granted ||
|
|
newStatus == PermissionStatus.provisional) {
|
|
Logger.i('PermissionUtil', '$permissionName 权限在请求后被授予');
|
|
return true;
|
|
} else if (newStatus == PermissionStatus.permanentlyDenied) {
|
|
Logger.w('PermissionUtil', '$permissionName 权限在请求后被永久拒绝');
|
|
await _showPermanentDenialDialog(
|
|
context: context,
|
|
permissionName: permissionName,
|
|
permanentDenialText: permanentDenialText,
|
|
settingsButtonText: settingsButtonText,
|
|
cancelButtonText: cancelButtonText,
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 处理受限状态
|
|
if (status == PermissionStatus.restricted ||
|
|
status == PermissionStatus.limited) {
|
|
Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status');
|
|
await _showRestrictedPermissionDialog(context, permissionName);
|
|
return false;
|
|
}
|
|
|
|
// 其他未知状态处理
|
|
Logger.e('PermissionUtil', '$permissionName 处于未知状态: $status');
|
|
return false;
|
|
} catch (e, stackTrace) {
|
|
Logger.e(
|
|
'PermissionUtil',
|
|
'请求 $permissionName 权限失败: $e',
|
|
e,
|
|
stackTrace,
|
|
);
|
|
_hideExplanationOverlay();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 显示权限解释横幅(漂浮在UI顶部的说明性UI)
|
|
///
|
|
/// 在请求权限前显示,向用户解释为什么需要这个权限
|
|
void _showExplanationOverlay({
|
|
required BuildContext context,
|
|
required String permissionName,
|
|
required String explanationText,
|
|
}) {
|
|
_hideExplanationOverlay(); // 确保移除旧叠加层
|
|
|
|
_explanationOverlayEntry = OverlayEntry(
|
|
builder: (context) => Positioned(
|
|
top: MediaQuery.of(context).padding.top + 10,
|
|
left: 16,
|
|
right: 16,
|
|
child: Material(
|
|
borderRadius: BorderRadius.circular(12),
|
|
elevation: 4,
|
|
color: Theme.of(context).colorScheme.background,
|
|
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() {
|
|
if (_explanationOverlayEntry != null) {
|
|
_explanationOverlayEntry?.remove();
|
|
_explanationOverlayEntry = null;
|
|
Logger.d('PermissionUtil', '权限说明叠加层已移除');
|
|
}
|
|
}
|
|
|
|
/// 显示永久拒绝后的设置引导对话框
|
|
///
|
|
/// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限
|
|
Future<void> _showPermanentDenialDialog({
|
|
required BuildContext context,
|
|
required String permissionName,
|
|
required String permanentDenialText,
|
|
required String settingsButtonText,
|
|
required String cancelButtonText,
|
|
}) async {
|
|
Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)');
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => AlertDialog(
|
|
title: Text('需要$permissionName权限'),
|
|
content: Text(permanentDenialText),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Logger.d('PermissionUtil', '用户取消设置 ($permissionName)');
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(cancelButtonText),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
Logger.d('PermissionUtil', '用户同意前往设置 ($permissionName)');
|
|
Navigator.pop(context);
|
|
AppSettings.openAppSettings();
|
|
},
|
|
child: Text(settingsButtonText),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 显示受限权限对话框
|
|
///
|
|
/// 在权限受限状态(如iOS上的受限照片访问)时调用
|
|
Future<void> _showRestrictedPermissionDialog(
|
|
BuildContext context, String permissionName) async {
|
|
Logger.w('PermissionUtil', '显示受限权限对话框 ($permissionName)');
|
|
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('确定'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|