Commit 6051f850830989a186dfe8ee0502297f93fdc399

Authored by 吴启风
1 parent b15fde72

feat:带隐私合规权限申请描述的权限申请页封装(独立的透明页)

lib/pages/reading/bloc/reading_bloc.dart
@@ -4,15 +4,16 @@ import 'package:flutter/foundation.dart'; @@ -4,15 +4,16 @@ import 'package:flutter/foundation.dart';
4 import 'package:flutter/services.dart'; 4 import 'package:flutter/services.dart';
5 import 'package:flutter_bloc/flutter_bloc.dart'; 5 import 'package:flutter_bloc/flutter_bloc.dart';
6 import 'package:flutter_easyloading/flutter_easyloading.dart'; 6 import 'package:flutter_easyloading/flutter_easyloading.dart';
  7 +import 'package:permission_handler/permission_handler.dart';
7 import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; 8 import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart';
8 9
9 import '../../../common/request/dao/listen_dao.dart'; 10 import '../../../common/request/dao/listen_dao.dart';
10 import '../../../common/request/exception.dart'; 11 import '../../../common/request/exception.dart';
11 import '../../../models/course_process_entity.dart'; 12 import '../../../models/course_process_entity.dart';
12 import '../../../utils/loading.dart'; 13 import '../../../utils/loading.dart';
13 -import 'dart:convert';  
14 14
15 import '../../../utils/log_util.dart'; 15 import '../../../utils/log_util.dart';
  16 +import '../permissionRequestPage.dart';
16 17
17 part 'reading_event.dart'; 18 part 'reading_event.dart';
18 part 'reading_state.dart'; 19 part 'reading_state.dart';
@@ -74,7 +75,9 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { @@ -74,7 +75,9 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> {
74 75
75 late AudioPlayer audioPlayer; 76 late AudioPlayer audioPlayer;
76 77
77 - ReadingPageBloc(this.pageController, this.courseLessonId) 78 + final BuildContext context;
  79 +
  80 + ReadingPageBloc(this.context, this.pageController, this.courseLessonId)
78 : super(ReadingPageInitial()) { 81 : super(ReadingPageInitial()) {
79 on<CurrentPageIndexChangeEvent>(_pageControllerChange); 82 on<CurrentPageIndexChangeEvent>(_pageControllerChange);
80 on<CurrentModeChangeEvent>(_playModeChange); 83 on<CurrentModeChangeEvent>(_playModeChange);
@@ -112,7 +115,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -112,7 +115,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
112 const MethodChannel('wow_english/sing_sound_method_channel'); 115 const MethodChannel('wow_english/sing_sound_method_channel');
113 methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 116 methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测
114 methodChannel.setMethodCallHandler((call) async { 117 methodChannel.setMethodCallHandler((call) async {
115 - Log.d("setMethodCallHandler method=${call.method} arguments=${call.arguments}"); 118 + Log.d(
  119 + "setMethodCallHandler method=${call.method} arguments=${call.arguments}");
116 if (call.method == 'voiceResult') { 120 if (call.method == 'voiceResult') {
117 //评测结果 121 //评测结果
118 add(XSVoiceResultEvent(call.arguments)); 122 add(XSVoiceResultEvent(call.arguments));
@@ -209,7 +213,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -209,7 +213,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
209 if (_isRecordAudioPlaying) { 213 if (_isRecordAudioPlaying) {
210 _isRecordAudioPlaying = false; 214 _isRecordAudioPlaying = false;
211 } 215 }
212 - Log.d("_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl"); 216 + Log.d(
  217 + "_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl");
213 if (_isOriginAudioPlaying) { 218 if (_isOriginAudioPlaying) {
214 _isOriginAudioPlaying = false; 219 _isOriginAudioPlaying = false;
215 await audioPlayer.stop(); 220 await audioPlayer.stop();
@@ -230,7 +235,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -230,7 +235,8 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
230 if (_isOriginAudioPlaying) { 235 if (_isOriginAudioPlaying) {
231 _isOriginAudioPlaying = false; 236 _isOriginAudioPlaying = false;
232 } 237 }
233 - Log.d("_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}"); 238 + Log.d(
  239 + "_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}");
234 if (_isRecordAudioPlaying) { 240 if (_isRecordAudioPlaying) {
235 _isRecordAudioPlaying = false; 241 _isRecordAudioPlaying = false;
236 await audioPlayer.stop(); 242 await audioPlayer.stop();
@@ -282,11 +288,16 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -282,11 +288,16 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
282 } 288 }
283 289
284 void startRecord(String content) async { 290 void startRecord(String content) async {
285 - if (_isRecording == true) {  
286 - return; 291 + // 调用封装好的权限检查和请求方法
  292 + bool result = await permissionCheckAndRequest(
  293 + context,
  294 + Permission.microphone,
  295 + "录音"
  296 + );
  297 + if (result) {
  298 + methodChannel.invokeMethod(
  299 + 'startVoice', {'word': content, 'type': '0', 'userId': '1'});
287 } 300 }
288 - methodChannel.invokeMethod(  
289 - 'startVoice', {'word': content, 'type': '0', 'userId': '1'});  
290 } 301 }
291 302
292 void _voiceXsResult( 303 void _voiceXsResult(
lib/pages/reading/permissionRequestPage.dart 0 → 100644
  1 +import 'dart:io';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:flutter/services.dart';
  4 +import 'package:permission_handler/permission_handler.dart';
  5 +
  6 +import '../../utils/log_util.dart';
  7 +
  8 +/// 权限检查及请求
  9 +/// 外部可通过此方法来进行权限的检查和请求,将自动跳转到`PermissionRequestPage`页面。
  10 +/// 传入 `Permission` 以及对应的权限名称 `permissionTypeStr`,如果有权限则返回 `Future true`
  11 +/// `isRequiredPermission` 如果为 `true`,则 "取消" 按钮将执行 "退出app" 的操作
  12 +Future<bool> permissionCheckAndRequest(
  13 + BuildContext context,
  14 + Permission permission,
  15 + String permissionTypeStr,
  16 + {bool isRequiredPermission = false}) async {
  17 + if (!await permission.status.isGranted) {
  18 + await Navigator.of(context).push(PageRouteBuilder(
  19 + opaque: true,
  20 + barrierColor: const Color.fromRGBO(255, 0, 0, 0.7),
  21 + pageBuilder: ((context, animation, secondaryAnimation) {
  22 + return PermissionRequestPage(permission, permissionTypeStr,
  23 + isRequiredPermission: isRequiredPermission);
  24 + })));
  25 + } else {
  26 + return true;
  27 + }
  28 + return false;
  29 +}
  30 +
  31 +class PermissionRequestPage extends StatefulWidget {
  32 + const PermissionRequestPage(this.permission, this.permissionTypeStr,
  33 + {super.key, this.isRequiredPermission = false});
  34 +
  35 + final Permission permission;
  36 + final String permissionTypeStr;
  37 + final bool isRequiredPermission;
  38 +
  39 + @override
  40 + State<PermissionRequestPage> createState() => _PermissionRequestPageState();
  41 +}
  42 +
  43 +class _PermissionRequestPageState extends State<PermissionRequestPage>
  44 + with WidgetsBindingObserver {
  45 + bool _isGoSetting = false;
  46 + late final List<String> msgList;
  47 + bool _isDialogShowing = false;
  48 +
  49 + @override
  50 + void initState() {
  51 + super.initState();
  52 + WidgetsBinding.instance.addObserver(this);
  53 + msgList = [
  54 + "${widget.permissionTypeStr}功能需要获取您设备的${widget.permissionTypeStr}权限,否则可能无法正常工作。\n是否申请${widget.permissionTypeStr}权限?",
  55 + "${widget.permissionTypeStr}权限不全,是否重新申请权限?",
  56 + "没有${widget.permissionTypeStr}权限,您可以手动开启权限",
  57 + widget.isRequiredPermission ? "退出应用" : "取消"
  58 + ];
  59 + _checkPermission(widget.permission);
  60 + }
  61 +
  62 + @override
  63 + void didChangeAppLifecycleState(AppLifecycleState state) {
  64 + super.didChangeAppLifecycleState(state);
  65 + Log.d("didChangeAppLifecycleState state=$state _isGoSetting=$_isGoSetting");
  66 + // 监听 app 从后台切回前台
  67 + if (state == AppLifecycleState.resumed && _isGoSetting) {
  68 + _checkPermission(widget.permission);
  69 + }
  70 + }
  71 +
  72 + /// 校验权限
  73 + void _checkPermission(Permission permission) async {
  74 + final PermissionStatus status = await permission.status;
  75 + _handlePermissionStatus(permission, status);
  76 + }
  77 +
  78 + void _handlePermissionStatus(Permission permission, PermissionStatus status) {
  79 + Log.d('_handlePermissionStatus=$status permission=$permission');
  80 + if (status.isGranted) {
  81 + _popPage();
  82 + return;
  83 + }
  84 +
  85 + // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`)
  86 + if (status.isDenied) {
  87 + showAlert(
  88 + permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "确定");
  89 + }
  90 + // 权限已被永久拒绝
  91 + if (status.isPermanentlyDenied) {
  92 + _isGoSetting = true;
  93 + showAlert(
  94 + permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "确定");
  95 + }
  96 + // 拥有部分权限
  97 + if (status.isLimited) {
  98 + if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true;
  99 + showAlert(
  100 + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "确定");
  101 + }
  102 + // 拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS)
  103 + if (status.isRestricted) {
  104 + if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true;
  105 + showAlert(
  106 + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "确定");
  107 + }
  108 + }
  109 +
  110 + void showAlert(Permission permission, String message, String cancelMsg,
  111 + String confirmMsg) {
  112 + if (_isDialogShowing) {
  113 + // 对话框已经在显示中,不重复弹出
  114 + Log.d("对话框已经在显示中,不重复弹出");
  115 + return;
  116 + }
  117 + _isDialogShowing = true;
  118 + showDialog(
  119 + context: context,
  120 + builder: (context) {
  121 + return AlertDialog(
  122 + title: const Text("温馨提示"),
  123 + content: Text(message),
  124 + actions: [
  125 + TextButton(
  126 + child: Text(cancelMsg),
  127 + onPressed: () {
  128 + widget.isRequiredPermission
  129 + ? _quitApp()
  130 + : _popDialogAndPage(context);
  131 + }),
  132 + TextButton(
  133 + child: Text(confirmMsg),
  134 + onPressed: () {
  135 + if (_isGoSetting) {
  136 + openAppSettings();
  137 + } else {
  138 + _requestPermisson(permission);
  139 + }
  140 + _popDialog(context);
  141 + })
  142 + ],
  143 + );
  144 + }).then((value) => {
  145 + _isDialogShowing = false,
  146 + });
  147 + }
  148 +
  149 + /// 申请权限
  150 + void _requestPermisson(Permission permission) async {
  151 + // 申请权限
  152 + PermissionStatus status = await permission.request();
  153 + Log.d('requestPermisson权限检测=$status _isGoSetting=$_isGoSetting');
  154 + // 再次校验
  155 + _handlePermissionStatus(permission, status);
  156 + }
  157 +
  158 + @override
  159 + void dispose() {
  160 + WidgetsBinding.instance.removeObserver(this);
  161 + super.dispose();
  162 + }
  163 +
  164 + @override
  165 + Widget build(BuildContext context) {
  166 + return Container();
  167 + }
  168 +
  169 + /// 退出应用程序
  170 + void _quitApp() {
  171 + SystemChannels.platform.invokeMethod("SystemNavigator.pop");
  172 + }
  173 +
  174 + /// 关闭整个权限申请页面
  175 + void _popDialogAndPage(BuildContext dialogContext) {
  176 + _popDialog(dialogContext);
  177 + _popPage();
  178 + }
  179 +
  180 + /// 关闭弹窗
  181 + void _popDialog(BuildContext dialogContext) {
  182 + Navigator.of(dialogContext).pop();
  183 + }
  184 +
  185 + /// 关闭透明页面
  186 + void _popPage() {
  187 + Navigator.of(context).pop();
  188 + }
  189 +}
lib/pages/reading/reading_page.dart
@@ -18,7 +18,7 @@ class ReadingPage extends StatelessWidget { @@ -18,7 +18,7 @@ class ReadingPage extends StatelessWidget {
18 @override 18 @override
19 Widget build(BuildContext context) { 19 Widget build(BuildContext context) {
20 return BlocProvider( 20 return BlocProvider(
21 - create: (_) => ReadingPageBloc(PageController(), courseLessonId ?? '') 21 + create: (_) => ReadingPageBloc(context, PageController(), courseLessonId ?? '')
22 ..add(InitBlocEvent()) 22 ..add(InitBlocEvent())
23 ..add(RequestDataEvent()), 23 ..add(RequestDataEvent()),
24 child: _ReadingPage(), 24 child: _ReadingPage(),