Commit 1daca20d5640a9c1ca1059dd67d2d5f74e44808a
1 parent
1f5969b8
pref:权限申请页面优化
Showing
3 changed files
with
124 additions
and
50 deletions
lib/common/permission/permissionRequestPage.dart
| 1 | +import 'dart:async'; | |
| 1 | 2 | import 'dart:io'; | 
| 2 | 3 | import 'package:flutter/material.dart'; | 
| 4 | +import 'package:package_info_plus/package_info_plus.dart'; | |
| 3 | 5 | import 'package:permission_handler/permission_handler.dart'; | 
| 4 | 6 | import 'package:wow_english/common/core/app_config_helper.dart'; | 
| 5 | 7 | |
| 6 | 8 | import '../../utils/log_util.dart'; | 
| 7 | 9 | |
| 8 | -/// 权限检查及请求 | |
| 10 | +/// 带隐私合规功能的权限检查及请求 | |
| 9 | 11 | /// 外部可通过此方法来进行权限的检查和请求,将自动跳转到`PermissionRequestPage`页面。 | 
| 10 | 12 | /// 传入 `Permission` 以及对应的权限名称 `permissionTypeStr`,如果有权限则返回 `Future true` | 
| 11 | 13 | /// `isRequiredPermission` 如果为 `true`,则 "取消" 按钮将执行 "退出app" 的操作 | 
| 14 | +Future<bool> permissionCheckAndRequests( | |
| 15 | + BuildContext context, | |
| 16 | + List<Permission> permissions, | |
| 17 | + List<String> permissionTypeStrs, | |
| 18 | + {bool isRequiredPermission = false}) async { | |
| 19 | + | |
| 20 | + // List<PermissionStatus> statuses = await Future.wait( | |
| 21 | + // permissions.map((permission) => permission.status), | |
| 22 | + // ); | |
| 23 | + // bool allGranted = statuses.every((status) => status.isGranted); | |
| 24 | + | |
| 25 | + bool allGranted = await isPermissionsGranted(permissions); | |
| 26 | + if (allGranted) { | |
| 27 | + return true; | |
| 28 | + } else { | |
| 29 | + return await Navigator.of(context).push(PageRouteBuilder( | |
| 30 | + opaque: false, | |
| 31 | + pageBuilder: ((context, animation, secondaryAnimation) { | |
| 32 | + return PermissionRequestPage(permissions, permissionTypeStrs, | |
| 33 | + isRequiredPermission: isRequiredPermission); | |
| 34 | + }))); | |
| 35 | + } | |
| 36 | +} | |
| 37 | + | |
| 12 | 38 | Future<bool> permissionCheckAndRequest( | 
| 13 | 39 | BuildContext context, | 
| 14 | 40 | Permission permission, | 
| 15 | 41 | String permissionTypeStr, | 
| 16 | 42 | {bool isRequiredPermission = false}) async { | 
| 17 | - if (!await permission.status.isGranted) { | |
| 18 | - await Navigator.of(context).push(PageRouteBuilder( | |
| 19 | - opaque: false, | |
| 20 | - pageBuilder: ((context, animation, secondaryAnimation) { | |
| 21 | - return PermissionRequestPage(permission, permissionTypeStr, | |
| 22 | - isRequiredPermission: isRequiredPermission); | |
| 23 | - }))); | |
| 24 | - } else { | |
| 25 | - return true; | |
| 43 | + return permissionCheckAndRequests(context, [permission], [permissionTypeStr], | |
| 44 | + isRequiredPermission: isRequiredPermission); | |
| 45 | + } | |
| 46 | + | |
| 47 | +///判断权限数组是否都授予 | |
| 48 | +Future<bool> isPermissionsGranted(List<Permission> permissions) async { | |
| 49 | + // 使用 every 直接检查权限状态 | |
| 50 | + return await Future.wait(permissions.map((permission) async { | |
| 51 | + return await permission.status.isGranted; | |
| 52 | + })).then((statuses) => statuses.every((status) => status)); | |
| 53 | +} | |
| 54 | + | |
| 55 | +///请求权限 | |
| 56 | +Future<MapEntry<Permission, PermissionStatus>?> requestPermissions(List<Permission> permissionList) async { | |
| 57 | + Map<Permission, PermissionStatus> statusesMap = await permissionList.request(); | |
| 58 | + for (var entry in statusesMap.entries) { | |
| 59 | + if (!entry.value.isGranted) { | |
| 60 | + return entry; | |
| 61 | + } | |
| 26 | 62 | } | 
| 27 | - return false; | |
| 63 | + return null; | |
| 28 | 64 | } | 
| 29 | 65 | |
| 30 | 66 | class PermissionRequestPage extends StatefulWidget { | 
| 31 | - const PermissionRequestPage(this.permission, this.permissionTypeStr, | |
| 67 | + const PermissionRequestPage(this.permissions, this.permissionTypeStrs, | |
| 32 | 68 | {super.key, this.isRequiredPermission = false}); | 
| 33 | 69 | |
| 34 | - final Permission permission; | |
| 35 | - final String permissionTypeStr; | |
| 70 | + final List<Permission> permissions; | |
| 71 | + final List<String> permissionTypeStrs; | |
| 72 | + ///是否需要强制授予 | |
| 36 | 73 | final bool isRequiredPermission; | 
| 37 | 74 | |
| 38 | 75 | @override | 
| ... | ... | @@ -49,13 +86,14 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 49 | 86 | void initState() { | 
| 50 | 87 | super.initState(); | 
| 51 | 88 | WidgetsBinding.instance.addObserver(this); | 
| 89 | + String permissionTypeStrs = widget.permissionTypeStrs.join('、'); | |
| 52 | 90 | msgList = [ | 
| 53 | - "${widget.permissionTypeStr}功能需要获取您设备的${widget.permissionTypeStr}权限,否则可能无法正常工作。\n是否申请${widget.permissionTypeStr}权限?", | |
| 54 | - "${widget.permissionTypeStr}权限不全,是否重新申请权限?", | |
| 55 | - "没有${widget.permissionTypeStr}权限,您可以手动开启权限", | |
| 91 | + "Wow English需要获取您设备的$permissionTypeStrs权限,否则可能无法正常工作。\n是否同意授予?", | |
| 92 | + "$permissionTypeStrs权限不全,是否重新申请权限?", | |
| 93 | + "没有$permissionTypeStrs权限,您可以手动开启权限", | |
| 56 | 94 | widget.isRequiredPermission ? "退出应用" : "取消" | 
| 57 | 95 | ]; | 
| 58 | - _checkPermission(widget.permission); | |
| 96 | + _handlePermission(widget.permissions); | |
| 59 | 97 | } | 
| 60 | 98 | |
| 61 | 99 | @override | 
| ... | ... | @@ -64,35 +102,63 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 64 | 102 | Log.d("didChangeAppLifecycleState state=$state _isGoSetting=$_isGoSetting"); | 
| 65 | 103 | // 监听 app 从后台切回前台 | 
| 66 | 104 | if (state == AppLifecycleState.resumed && _isGoSetting) { | 
| 67 | - _checkPermission(widget.permission); | |
| 105 | + _handlePermission(widget.permissions); | |
| 68 | 106 | } | 
| 69 | 107 | } | 
| 70 | 108 | |
| 71 | 109 | /// 校验权限 | 
| 72 | - void _checkPermission(Permission permission) async { | |
| 73 | - final PermissionStatus status = await permission.status; | |
| 74 | - _handlePermissionStatus(permission, status); | |
| 110 | + void _handlePermission(List<Permission> permissions) async { | |
| 111 | + ///一个新待申请权限列表 | |
| 112 | + List<Permission> intentPermissionList = []; | |
| 113 | + | |
| 114 | + ///遍历当前权限申请列表 | |
| 115 | + for (Permission permission in permissions) { | |
| 116 | + PermissionStatus status = await permission.status; | |
| 117 | + | |
| 118 | + ///如果不是允许状态就添加到新的申请列表中 | |
| 119 | + if (!status.isGranted) { | |
| 120 | + intentPermissionList.add(permission); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + if (intentPermissionList.isEmpty) { | |
| 125 | + _popPage(true); | |
| 126 | + } else { | |
| 127 | + _requestPermission(intentPermissionList); | |
| 128 | + } | |
| 75 | 129 | } | 
| 76 | 130 | |
| 77 | - void _handlePermissionStatus(Permission permission, PermissionStatus status) { | |
| 78 | - Log.d('_handlePermissionStatus=$status permission=$permission'); | |
| 79 | - if (status.isGranted) { | |
| 80 | - _popPage(); | |
| 131 | + ///实际触发请求权限 | |
| 132 | + Future<void> _requestPermission(List<Permission> permissions) async { | |
| 133 | + Log.d('_requestPermission permissions=$permissions'); | |
| 134 | + if (await isPermissionsGranted(permissions)) { | |
| 135 | + _popPage(true); | |
| 81 | 136 | return; | 
| 82 | 137 | } | 
| 83 | 138 | |
| 139 | + MapEntry<Permission, PermissionStatus>? statusEntry = await requestPermissions(permissions); | |
| 140 | + if (statusEntry == null) { | |
| 141 | + ///都手动同意授予了 | |
| 142 | + _popPage(true); | |
| 143 | + return; | |
| 144 | + } | |
| 145 | + | |
| 146 | + Permission permission = statusEntry.key; | |
| 147 | + PermissionStatus status = statusEntry.value; | |
| 84 | 148 | // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) | 
| 85 | 149 | if (status.isDenied) { | 
| 86 | 150 | showAlert( | 
| 87 | 151 | permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); | 
| 88 | 152 | } | 
| 89 | 153 | // 权限已被永久拒绝 | 
| 154 | + /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 | |
| 155 | + /// 在 iOS 上:如果用户拒绝访问所请求的功能。 | |
| 90 | 156 | if (status.isPermanentlyDenied) { | 
| 91 | 157 | _isGoSetting = true; | 
| 92 | 158 | showAlert( | 
| 93 | 159 | permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); | 
| 94 | 160 | } | 
| 95 | - // 拥有部分权限 | |
| 161 | + // 拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) | |
| 96 | 162 | if (status.isLimited) { | 
| 97 | 163 | if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; | 
| 98 | 164 | showAlert( | 
| ... | ... | @@ -126,7 +192,7 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 126 | 192 | onPressed: () { | 
| 127 | 193 | widget.isRequiredPermission | 
| 128 | 194 | ? _quitApp() | 
| 129 | - : _popDialogAndPage(context); | |
| 195 | + : _popDialogAndPage(context, false); | |
| 130 | 196 | }), | 
| 131 | 197 | TextButton( | 
| 132 | 198 | child: Text(confirmMsg), | 
| ... | ... | @@ -134,7 +200,7 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 134 | 200 | if (_isGoSetting) { | 
| 135 | 201 | openAppSettings(); | 
| 136 | 202 | } else { | 
| 137 | - _requestPermisson(permission); | |
| 203 | + _handlePermission(widget.permissions); | |
| 138 | 204 | } | 
| 139 | 205 | _popDialog(context); | 
| 140 | 206 | }) | 
| ... | ... | @@ -145,15 +211,6 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 145 | 211 | }); | 
| 146 | 212 | } | 
| 147 | 213 | |
| 148 | - /// 申请权限 | |
| 149 | - void _requestPermisson(Permission permission) async { | |
| 150 | - // 申请权限 | |
| 151 | - PermissionStatus status = await permission.request(); | |
| 152 | - Log.d('requestPermisson权限检测=$status _isGoSetting=$_isGoSetting'); | |
| 153 | - // 再次校验 | |
| 154 | - _handlePermissionStatus(permission, status); | |
| 155 | - } | |
| 156 | - | |
| 157 | 214 | @override | 
| 158 | 215 | void dispose() { | 
| 159 | 216 | WidgetsBinding.instance.removeObserver(this); | 
| ... | ... | @@ -171,9 +228,9 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 171 | 228 | } | 
| 172 | 229 | |
| 173 | 230 | /// 关闭整个权限申请页面 | 
| 174 | - void _popDialogAndPage(BuildContext dialogContext) { | |
| 231 | + void _popDialogAndPage(BuildContext dialogContext, bool isAllGranted) { | |
| 175 | 232 | _popDialog(dialogContext); | 
| 176 | - _popPage(); | |
| 233 | + _popPage(isAllGranted); | |
| 177 | 234 | } | 
| 178 | 235 | |
| 179 | 236 | /// 关闭弹窗 | 
| ... | ... | @@ -182,7 +239,7 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> | 
| 182 | 239 | } | 
| 183 | 240 | |
| 184 | 241 | /// 关闭透明页面 | 
| 185 | - void _popPage() { | |
| 186 | - Navigator.of(context).pop(); | |
| 242 | + void _popPage(bool isAllGranted) { | |
| 243 | + Navigator.of(context).pop(isAllGranted); | |
| 187 | 244 | } | 
| 188 | 245 | } | ... | ... | 
lib/pages/user/modify/modify_user_avatar_page.dart
| ... | ... | @@ -22,7 +22,7 @@ class ModifyUserAvatarPage extends StatelessWidget { | 
| 22 | 22 | @override | 
| 23 | 23 | Widget build(BuildContext context) { | 
| 24 | 24 | return BlocProvider( | 
| 25 | - create: (context) => UserAvatarBloc(), | |
| 25 | + create: (context) => UserAvatarBloc(context), | |
| 26 | 26 | child: _ModifyUserAvatarPage(pageType: pageType), | 
| 27 | 27 | ); | 
| 28 | 28 | } | ... | ... | 
lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart
| ... | ... | @@ -14,6 +14,8 @@ import 'package:wow_english/utils/aliyun_oss_util.dart'; | 
| 14 | 14 | import 'package:wow_english/utils/log_util.dart'; | 
| 15 | 15 | import 'package:wow_english/utils/toast_util.dart'; | 
| 16 | 16 | |
| 17 | +import '../../../../common/permission/permissionRequestPage.dart'; | |
| 18 | + | |
| 17 | 19 | part 'user_avatar_event.dart'; | 
| 18 | 20 | part 'user_avatar_state.dart'; | 
| 19 | 21 | |
| ... | ... | @@ -32,7 +34,9 @@ class UserAvatarBloc extends Bloc<UserAvatarEvent, UserAvatarState> { | 
| 32 | 34 | |
| 33 | 35 | final ImagePicker picker = ImagePicker(); | 
| 34 | 36 | |
| 35 | - UserAvatarBloc() : super(UserAvatarInitial()) { | |
| 37 | + final BuildContext context; | |
| 38 | + | |
| 39 | + UserAvatarBloc(this.context) : super(UserAvatarInitial()) { | |
| 36 | 40 | on<GetImageFromPhotoEvent>(_getImageFromPhoto); | 
| 37 | 41 | on<GetImageFromCameraEvent>(_getImageFromCamera); | 
| 38 | 42 | on<ChangeUserEnterAppStateEvent>(_changeUserEnterAppState); | 
| ... | ... | @@ -75,11 +79,8 @@ class UserAvatarBloc extends Bloc<UserAvatarEvent, UserAvatarState> { | 
| 75 | 79 | } | 
| 76 | 80 | |
| 77 | 81 | void _getImageFromCamera(GetImageFromCameraEvent event, Emitter<UserAvatarState> emitter) async { | 
| 78 | - await getPermissionStatus(Permission.camera).then((value) async { | |
| 79 | - if (!value) { | |
| 80 | - debugPrint('失败$value'); | |
| 81 | - return; | |
| 82 | - } | |
| 82 | + bool result = await permissionCheckAndRequest(context, Permission.camera, "拍照"); | |
| 83 | + if (result) { | |
| 83 | 84 | _file = await picker.pickImage(source: ImageSource.camera); | 
| 84 | 85 | EasyLoading.show(); | 
| 85 | 86 | try { | 
| ... | ... | @@ -90,7 +91,23 @@ class UserAvatarBloc extends Bloc<UserAvatarEvent, UserAvatarState> { | 
| 90 | 91 | showToast('上传头像失败: $e'); | 
| 91 | 92 | } | 
| 92 | 93 | EasyLoading.dismiss(); | 
| 93 | - }); | |
| 94 | + } | |
| 95 | + // await getPermissionStatus(Permission.camera).then((value) async { | |
| 96 | + // if (!value) { | |
| 97 | + // debugPrint('失败$value'); | |
| 98 | + // return; | |
| 99 | + // } | |
| 100 | + // _file = await picker.pickImage(source: ImageSource.camera); | |
| 101 | + // EasyLoading.show(); | |
| 102 | + // try { | |
| 103 | + // final urlStr = await _uploadAvatar(_file!.path); | |
| 104 | + // emitter(ChangeImageState(urlStr)); | |
| 105 | + // } catch (e) { | |
| 106 | + // Log.e('上传头像失败:$e'); | |
| 107 | + // showToast('上传头像失败: $e'); | |
| 108 | + // } | |
| 109 | + // EasyLoading.dismiss(); | |
| 110 | + // }); | |
| 94 | 111 | } | 
| 95 | 112 | |
| 96 | 113 | void _changeUserEnterAppState(ChangeUserEnterAppStateEvent event, Emitter<UserAvatarState> emitter) async { | ... | ... | 
