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 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 _showPermanentDenialDialog({ required BuildContext context, required String permissionName, required String permanentDenialText, required String settingsButtonText, required String cancelButtonText, }) async { Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)'); await showDialog( 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 _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('确定'), ), ], ), ); } }