import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:app_settings/app_settings.dart'; import 'logger.dart'; import 'package:get/get.dart'; /// 权限请求工具类 /// /// 提供统一的权限请求流程处理: /// 1. 初始权限状态检查 /// 2. 权限解释横幅显示(悬浮在UI顶部的解释性UI) /// 3. 权限请求执行 /// 4. 永久拒绝后的设置引导 /// /// 使用单例模式:`PermissionUtil.instance` class PermissionUtil { /// 单例实例 static final PermissionUtil instance = PermissionUtil._(); /// 私有构造函数 PermissionUtil._(); /// 权限解释横幅的叠加层入口 //OverlayEntry? _explanationOverlayEntry; /// 请求指定权限 /// /// 参数: /// [context]: BuildContext,用于UI显示和导航 /// [permissionType]: 要请求的权限类型(Permission.microphone等) /// [permissionName]: 权限的用户友好名称(如"麦克风"、"定位") /// [explanationText]: 权限用途的详细解释文本 /// [permanentDenialText]: 永久拒绝后引导用户去设置的文案 /// [settingsButtonText]: "去设置"按钮的文本(可选,默认"前往设置") /// [cancelButtonText]: "取消"按钮的文本(可选,默认"取消") /// /// 返回值:`Future` 权限是否最终授予 Future requestPermission({ // required BuildContext context, required Permission permissionType, required String permissionName, required String explanationText, required String permanentDenialText, String? settingsButtonText, String? cancelButtonText, }) async { // 使用翻译键作为默认值 final String finalSettingsButtonText = settingsButtonText ?? 'goToSettings'.tr; final String finalCancelButtonText = cancelButtonText ?? 'cancel'.tr; 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; } // 显示权限解释横幅(覆盖所有状态的处理) _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.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 权限在请求后被授予'); _hideExplanationOverlay(); return true; } // 处理请求后变为永久拒绝 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; } // 其他拒绝状态 _hideExplanationOverlay(); return false; } // 处理受限状态 if (status == PermissionStatus.restricted || status == PermissionStatus.limited) { Logger.w('PermissionUtil', '$permissionName 权限受限或受限访问: $status'); await _showRestrictedPermissionDialog(permissionName); _hideExplanationOverlay(); return false; } // 其他未知状态处理 Logger.e('PermissionUtil', '$permissionName 处于未知状态: $status'); _hideExplanationOverlay(); return false; } catch (e, stackTrace) { Logger.e( 'PermissionUtil', '请求 $permissionName 权限失败: $e', e, stackTrace, ); _hideExplanationOverlay(); return false; } } /// 显示权限解释横幅(漂浮在UI顶部的说明性UI) /// /// 在请求权限前显示,向用户解释为什么需要这个权限 void _showExplanationOverlay({ required String permissionName, required String explanationText, }) { Get.dialog( Stack( children: [ Positioned( top: 10, left: 16, right: 16, child: Material( borderRadius: BorderRadius.circular(12), elevation: 8, color: Colors.grey[300], shadowColor: Colors.black.withOpacity(0.3), child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 8), Text( explanationText, style: const TextStyle(fontSize: 14), ), ], ), ), ), ), ], ), ); // _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', '权限说明叠加层已移除'); } /// 显示永久拒绝后的设置引导对话框 /// /// 在权限被永久拒绝时调用,提示用户前往设置手动开启权限 Future _showPermanentDenialDialog({ //required BuildContext context, required String permissionName, required String permanentDenialText, required String settingsButtonText, required String cancelButtonText, }) async { Logger.i('PermissionUtil', '显示永久拒绝对话框 ($permissionName)'); bool? userChoice = await Get.dialog( AlertDialog( title: Text('permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr), content: Text(permanentDenialText), actions: [ TextButton( onPressed: () { Logger.d('PermissionUtil', '用户取消设置 ($permissionName)'); Get.back(result: false); }, child: Text(cancelButtonText), ), ElevatedButton( onPressed: () { Logger.d('PermissionUtil', '用户同意前往设置 ($permissionName)'); Get.back(result: true); }, child: Text(settingsButtonText), ), ], ), barrierDismissible: false, ); // bool? userChoice = await showDialog( // context: context, // barrierDismissible: false, // builder: (context) => // ); // 用户选择前往设置 if (userChoice == true) { AppSettings.openAppSettings(); return false; // 不能立即获取权限,需要用户手动设置 } // 用户取消 return false; } /// 显示受限权限对话框 /// /// 在权限受限状态(如iOS上的受限照片访问)时调用 Future _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('确定'), // ), // ], // ), // ); } }