From e811f16468de569f388a6e2e88e8ddea9c680a0f Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Sat, 29 Jun 2024 18:09:39 +0800 Subject: [PATCH] feat:权限申请页面增加隐私合规弹窗(android) --- lib/common/permission/permissionRequestPage.dart | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------- lib/common/permission/permissionRequester.dart | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/pages/practice/bloc/topic_picture_bloc.dart | 4 ++-- lib/pages/reading/bloc/reading_bloc.dart | 3 ++- lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart | 7 ++++--- 5 files changed, 156 insertions(+), 106 deletions(-) create mode 100644 lib/common/permission/permissionRequester.dart diff --git a/lib/common/permission/permissionRequestPage.dart b/lib/common/permission/permissionRequestPage.dart index 7b41db9..184633e 100644 --- a/lib/common/permission/permissionRequestPage.dart +++ b/lib/common/permission/permissionRequestPage.dart @@ -1,74 +1,22 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:wow_english/common/core/app_config_helper.dart'; +import 'package:wow_english/common/permission/permissionRequester.dart'; import '../../utils/log_util.dart'; -/// 带隐私合规功能的权限检查及请求 -/// 外部可通过此方法来进行权限的检查和请求,将自动跳转到`PermissionRequestPage`页面。 -/// 传入 `Permission` 以及对应的权限名称 `permissionTypeStr`,如果有权限则返回 `Future true` -/// `isRequiredPermission` 如果为 `true`,则 "取消" 按钮将执行 "退出app" 的操作 -Future permissionCheckAndRequests( - BuildContext context, - List permissions, - List permissionTypeStrs, - {bool isRequiredPermission = false}) async { - - // List statuses = await Future.wait( - // permissions.map((permission) => permission.status), - // ); - // bool allGranted = statuses.every((status) => status.isGranted); - - bool allGranted = await isPermissionsGranted(permissions); - if (allGranted) { - return true; - } else { - return await Navigator.of(context).push(PageRouteBuilder( - opaque: false, - pageBuilder: ((context, animation, secondaryAnimation) { - return PermissionRequestPage(permissions, permissionTypeStrs, - isRequiredPermission: isRequiredPermission); - }))); - } -} - -Future permissionCheckAndRequest( - BuildContext context, - Permission permission, - String permissionTypeStr, - {bool isRequiredPermission = false}) async { - return permissionCheckAndRequests(context, [permission], [permissionTypeStr], - isRequiredPermission: isRequiredPermission); - } - -///判断权限数组是否都授予 -Future isPermissionsGranted(List permissions) async { - // 使用 every 直接检查权限状态 - return await Future.wait(permissions.map((permission) async { - return await permission.status.isGranted; - })).then((statuses) => statuses.every((status) => status)); -} - -///请求权限 -Future?> requestPermissions(List permissionList) async { - Map statusesMap = await permissionList.request(); - for (var entry in statusesMap.entries) { - if (!entry.value.isGranted) { - return entry; - } - } - return null; -} - class PermissionRequestPage extends StatefulWidget { - const PermissionRequestPage(this.permissions, this.permissionTypeStrs, + const PermissionRequestPage(this.permissions, + this.permissionNames, this.permissionDesc, {super.key, this.isRequiredPermission = false}); final List permissions; - final List permissionTypeStrs; + final List permissionNames; + final String permissionDesc; + ///是否需要强制授予 final bool isRequiredPermission; @@ -86,22 +34,72 @@ class _PermissionRequestPageState extends State void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - String permissionTypeStrs = widget.permissionTypeStrs.join('、'); + String permissionDesc = widget.permissionDesc; + String permissionStr = widget.permissionNames.join('、'); msgList = [ - "Wow English需要获取您设备的$permissionTypeStrs权限,否则可能无法正常工作。\n是否同意授予?", - "$permissionTypeStrs权限不全,是否重新申请权限?", - "没有$permissionTypeStrs权限,您可以手动开启权限", - widget.isRequiredPermission ? "退出应用" : "取消" + "$permissionDesc,需要获取您设备的$permissionStr权限", + "你还没有开启$permissionStr权限,开启后即可$permissionDesc", + "未开启$permissionStr权限导致功能受限,您可以手动开启权限", + widget.isRequiredPermission ? "退出应用" : "以后再说" ]; _handlePermission(widget.permissions); } @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + // PackageInfo packageInfo = await PackageInfo.fromPlatform(); + // String packageName = packageInfo.packageName; + return Scaffold( + backgroundColor: Colors.transparent, + body: Align( + alignment: Alignment.topCenter, + child: Container( + width: screenWidth / 2, + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.0), // 圆角半径 + boxShadow: const [ + BoxShadow( + color: Colors.black26, + blurRadius: 4.0, + offset: Offset(2, 2), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Text( + '${widget.permissionNames.join('、')}权限使用说明', + style: TextStyle( + color: Colors.black, + fontSize: 15.sp, + fontWeight: FontWeight.w500, + fontFamily: 'PingFangSC-Regular'), + textAlign: TextAlign.left, + ), + ), + 16.verticalSpace, + Text( + widget.permissionDesc, + style: const TextStyle(color: Colors.black54, + fontFamily: 'PingFangSC-Regular'), + textAlign: TextAlign.left, + ), + ], + )))); + } + + @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); Log.d("didChangeAppLifecycleState state=$state _isGoSetting=$_isGoSetting"); // 监听 app 从后台切回前台 - if (state == AppLifecycleState.resumed && _isGoSetting) { + if (state == AppLifecycleState.resumed && _isGoSetting && !_isDialogShowing) { _handlePermission(widget.permissions); } } @@ -131,12 +129,8 @@ class _PermissionRequestPageState extends State ///实际触发请求权限 Future _requestPermission(List permissions) async { Log.d('_requestPermission permissions=$permissions'); - if (await isPermissionsGranted(permissions)) { - _popPage(true); - return; - } - - MapEntry? statusEntry = await requestPermissions(permissions); + MapEntry? statusEntry = + await requestPermissionsInner(permissions); if (statusEntry == null) { ///都手动同意授予了 _popPage(true); @@ -145,10 +139,11 @@ class _PermissionRequestPageState extends State Permission permission = statusEntry.key; PermissionStatus status = statusEntry.value; + // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) if (status.isDenied) { showAlert( - permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); + permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); } // 权限已被永久拒绝 /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 @@ -156,19 +151,14 @@ class _PermissionRequestPageState extends State if (status.isPermanentlyDenied) { _isGoSetting = true; showAlert( - permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); + permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); } - // 拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) - if (status.isLimited) { + // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) + // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) + if (status.isLimited || status.isRestricted) { if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; showAlert( - permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); - } - // 拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) - if (status.isRestricted) { - if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; - showAlert( - permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "确定"); + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); } } @@ -192,7 +182,7 @@ class _PermissionRequestPageState extends State onPressed: () { widget.isRequiredPermission ? _quitApp() - : _popDialogAndPage(context, false); + : _popDialog(context, false); }), TextButton( child: Text(confirmMsg), @@ -202,13 +192,14 @@ class _PermissionRequestPageState extends State } else { _handlePermission(widget.permissions); } - _popDialog(context); + _popDialog(context, null); }) ], ); }).then((value) => { _isDialogShowing = false, - }); + _popPage(value) + }); } @override @@ -217,29 +208,23 @@ class _PermissionRequestPageState extends State super.dispose(); } - @override - Widget build(BuildContext context) { - return Container(); - } - /// 退出应用程序 void _quitApp() { AppConfigHelper.exitApp(); } - /// 关闭整个权限申请页面 - void _popDialogAndPage(BuildContext dialogContext, bool isAllGranted) { - _popDialog(dialogContext); - _popPage(isAllGranted); - } - /// 关闭弹窗 - void _popDialog(BuildContext dialogContext) { + /// isAllGranted为null的话跳到系统设置页 + void _popDialog(BuildContext dialogContext, bool? isAllGranted) { Navigator.of(dialogContext).pop(); + _popPage(isAllGranted); } - /// 关闭透明页面 - void _popPage(bool isAllGranted) { - Navigator.of(context).pop(isAllGranted); + /// 关闭权限申请透明页面 + /// isAllGranted 所有权限都授予,为空不关闭 + void _popPage(bool? isAllGranted) { + if (isAllGranted != null) { + Navigator.of(context).pop(isAllGranted); + } } } diff --git a/lib/common/permission/permissionRequester.dart b/lib/common/permission/permissionRequester.dart new file mode 100644 index 0000000..66134ab --- /dev/null +++ b/lib/common/permission/permissionRequester.dart @@ -0,0 +1,63 @@ +import 'package:flutter/cupertino.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:wow_english/common/permission/permissionRequestPage.dart'; + +/// 带隐私合规功能的权限检查及请求入口 +/// 外部可通过此方法来进行权限的检查和请求,将自动跳转到`PermissionRequestPage`页面。 +/// 传入 `Permission` 以及对应的权限名称 `permissionTypeStr`,如果有权限则返回 `Future true` +/// permissionDesc:设备权限使用说明(描述) +/// isRequiredPermission:是否强制要求 如果为 `true`,则 "取消" 按钮将执行 "退出app" 的操作 +Future requestPermission( + BuildContext context, + Permission permission, + String permissionName, + String permissionDesc, + {bool isRequiredPermission = false}) async { + return requestPermissions(context, [permission], [permissionName], permissionDesc, + isRequiredPermission: isRequiredPermission); +} + + +Future requestPermissions( + BuildContext context, + List permissions, + List permissionNames, + String permissionDesc, + {bool isRequiredPermission = false}) async { + + // List statuses = await Future.wait( + // permissions.map((permission) => permission.status), + // ); + // bool allGranted = statuses.every((status) => status.isGranted); + + bool allGranted = await isPermissionsGranted(permissions); + if (allGranted) { + return true; + } else { + return await Navigator.of(context).push(PageRouteBuilder( + opaque: false, + pageBuilder: ((context, animation, secondaryAnimation) { + return PermissionRequestPage(permissions, permissionNames, permissionDesc, + isRequiredPermission: isRequiredPermission); + }))); + } +} + +///(实际请求前)判断权限数组是否都授予 +Future isPermissionsGranted(List permissions) async { + // 使用 every 直接检查权限状态 + return await Future.wait(permissions.map((permission) async { + return await permission.status.isGranted; + })).then((statuses) => statuses.every((status) => status)); +} + +///请求权限 +Future?> requestPermissionsInner(List permissionList) async { + Map statusesMap = await permissionList.request(); + for (var entry in statusesMap.entries) { + if (!entry.value.isGranted) { + return entry; + } + } + return null; +} \ No newline at end of file diff --git a/lib/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index 906941b..20aa694 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -15,7 +15,7 @@ import 'package:wow_english/pages/section/subsection/base_section/state.dart'; import 'package:wow_english/utils/loading.dart'; import 'package:wow_english/utils/toast_util.dart'; -import '../../../common/permission/permissionRequestPage.dart'; +import '../../../common/permission/permissionRequester.dart'; import '../../../route/route.dart'; part 'topic_picture_event.dart'; @@ -258,7 +258,7 @@ class TopicPictureBloc await audioPlayer.stop(); // 调用封装好的权限检查和请求方法 bool result = - await permissionCheckAndRequest(context, Permission.microphone, "录音"); + await requestPermission(context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); if (result) { methodChannel.invokeMethod('startVoice', { 'word': event.testWord, diff --git a/lib/pages/reading/bloc/reading_bloc.dart b/lib/pages/reading/bloc/reading_bloc.dart index e66fbbb..fcbd9a3 100644 --- a/lib/pages/reading/bloc/reading_bloc.dart +++ b/lib/pages/reading/bloc/reading_bloc.dart @@ -11,6 +11,7 @@ import 'package:wow_english/pages/section/subsection/base_section/event.dart'; import 'package:wow_english/pages/section/subsection/base_section/state.dart'; import '../../../common/core/user_util.dart'; +import '../../../common/permission/permissionRequester.dart'; import '../../../common/request/dao/listen_dao.dart'; import '../../../common/request/exception.dart'; import '../../../models/course_process_entity.dart'; @@ -336,7 +337,7 @@ class ReadingPageBloc void startRecord(String content) async { // 调用封装好的权限检查和请求方法 bool result = - await permissionCheckAndRequest(context, Permission.microphone, "录音"); + await requestPermission(context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); if (result) { methodChannel.invokeMethod('startVoice', { 'word': content, diff --git a/lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart b/lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart index cf61c3e..b7c8929 100644 --- a/lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart +++ b/lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart @@ -14,7 +14,7 @@ import 'package:wow_english/utils/aliyun_oss_util.dart'; import 'package:wow_english/utils/log_util.dart'; import 'package:wow_english/utils/toast_util.dart'; -import '../../../../common/permission/permissionRequestPage.dart'; +import '../../../../common/permission/permissionRequester.dart'; part 'user_avatar_event.dart'; part 'user_avatar_state.dart'; @@ -60,7 +60,8 @@ class UserAvatarBloc extends Bloc { } else { permission = Permission.photos; } - await getPermissionStatus(permission).then((value) async { + await requestPermission(context, permission, "文件读取", "用于在更换头像场景下从相册中选取图片等文件").then((value) async { + // await getPermissionStatus(permission).then((value) async { if (!value) { debugPrint('失败$value'); return; @@ -79,7 +80,7 @@ class UserAvatarBloc extends Bloc { } void _getImageFromCamera(GetImageFromCameraEvent event, Emitter emitter) async { - bool result = await permissionCheckAndRequest(context, Permission.camera, "拍照"); + bool result = await requestPermission(context, Permission.camera, "拍照", "用于在更换头像场景下调用相机拍照"); if (result) { _file = await picker.pickImage(source: ImageSource.camera); EasyLoading.show(); -- libgit2 0.22.2