Commit 0493c1044f4ffedea1543ff82814cd3f8f08d9b1

Authored by 吴启风
1 parent cd32eb01

feat:带音乐点击防抖函数优化

lib/common/utils/action_with_music_controller.dart renamed to lib/common/utils/click_with_music_controller.dart
  1 +import 'dart:async';
  2 +
1 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
2 4
3 import '../../utils/audio_player_util.dart'; 5 import '../../utils/audio_player_util.dart';
  6 +import '../../utils/log_util.dart';
4 7
5 /// action前播放音乐控制类,维护状态做防抖处理 8 /// action前播放音乐控制类,维护状态做防抖处理
6 /// todo 需要结合生命周期,尤其是在声明周期结束后及时中断,避免action泄漏 9 /// todo 需要结合生命周期,尤其是在声明周期结束后及时中断,避免action泄漏
7 -class ActionWithMusicController {  
8 - ActionWithMusicController._privateConstructor(); 10 +class ClickWithMusicController {
9 11
10 - static final ActionWithMusicController _instance = ActionWithMusicController._privateConstructor(); 12 + static ClickWithMusicController? _instance;
11 13
12 - factory ActionWithMusicController() {  
13 - return _instance;  
14 - } 14 + ClickWithMusicController._privateConstructor();
  15 +
  16 + static ClickWithMusicController get instance => _instance ??= ClickWithMusicController._privateConstructor();
15 17
16 bool _isPlaying = false; 18 bool _isPlaying = false;
17 19
  20 + ///@param action 可以是同步函数也可以是异步函数
18 Future<void> playMusicAndPerformAction(BuildContext context, 21 Future<void> playMusicAndPerformAction(BuildContext context,
19 - AudioPlayerUtilType audioType, Function action) async { 22 + AudioPlayerUtilType audioType, FutureOr<void> Function() action) async {
20 if (_isPlaying) return; 23 if (_isPlaying) return;
21 24
22 _isPlaying = true; 25 _isPlaying = true;
  26 + Log.d("WQF playMusicAndPerformAction playAudio begin");
23 27
24 // Play the music 28 // Play the music
25 await AudioPlayerUtil.getInstance() 29 await AudioPlayerUtil.getInstance()
26 .playAudio(audioType); 30 .playAudio(audioType);
27 31
28 - action();  
29 -  
30 - _isPlaying = false; 32 + try {
  33 + await Future.sync(action);
  34 + } catch (e) {
  35 + Log.d('WQF playMusicAndPerformAction exception $e');
  36 + } finally {
  37 + Log.d("WQF playMusicAndPerformAction playAudio end");
  38 + _isPlaying = false;
  39 + }
31 } 40 }
32 41
33 // void dispose() { 42 // void dispose() {
lib/pages/section/bloc/section_bloc.dart
  1 +import 'dart:async';
  2 +
1 import 'package:flutter/cupertino.dart'; 3 import 'package:flutter/cupertino.dart';
2 import 'package:flutter/foundation.dart'; 4 import 'package:flutter/foundation.dart';
3 import 'package:flutter/material.dart'; 5 import 'package:flutter/material.dart';
@@ -57,7 +59,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -57,7 +59,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
57 on<RequestEnterClassEvent>(_onEnterClass); 59 on<RequestEnterClassEvent>(_onEnterClass);
58 on<CurrentUnitIndexChangeEvent>(_pageControllerChange); 60 on<CurrentUnitIndexChangeEvent>(_pageControllerChange);
59 on<InitEvent>((event, emit) async { 61 on<InitEvent>((event, emit) async {
60 - await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe); 62 + await AudioPlayerUtil.getInstance()
  63 + .playAudio(AudioPlayerUtilType.countWithMe);
61 }); 64 });
62 } 65 }
63 66
@@ -87,20 +90,21 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -87,20 +90,21 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
87 90
88 ///进入课程接口请求函数封装 91 ///进入课程接口请求函数封装
89 ///onRequestEnterSuccess 接口请求成功后行为函数,比如跳转课程页面 92 ///onRequestEnterSuccess 接口请求成功后行为函数,比如跳转课程页面
90 - Future<void> requestEnterClass(String courseLessonId, Function onRequestEnterSuccess,  
91 - {Function? onRequestEnterFailed}) async { 93 + Future<void> requestEnterClass(
  94 + String courseLessonId, FutureOr<void> Function() onRequestEnterSuccess,
  95 + {FutureOr<void> Function(Object)? onRequestEnterFailed}) async {
92 await loading(() async { 96 await loading(() async {
93 try { 97 try {
94 await ListenDao.enterClass(courseLessonId); 98 await ListenDao.enterClass(courseLessonId);
95 - onRequestEnterSuccess(); 99 + await Future.sync(onRequestEnterSuccess);
96 } catch (e) { 100 } catch (e) {
97 if (e is ApiException) { 101 if (e is ApiException) {
98 - showToast(e.message ?? '请求失败,请检查网络连接'); 102 + showToast('进入课堂失败 ${e.message}');
99 } else { 103 } else {
100 - showToast('$e'); 104 + showToast('进入课堂失败 $e');
101 } 105 }
102 if (onRequestEnterFailed != null) { 106 if (onRequestEnterFailed != null) {
103 - onRequestEnterFailed(e); 107 + await Future.sync(onRequestEnterFailed(e) as FutureOr Function());
104 } 108 }
105 } 109 }
106 }); 110 });
lib/pages/section/section_page.dart
@@ -4,7 +4,7 @@ import &#39;package:flutter_bloc/flutter_bloc.dart&#39;; @@ -4,7 +4,7 @@ import &#39;package:flutter_bloc/flutter_bloc.dart&#39;;
4 import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 import 'package:flutter_screenutil/flutter_screenutil.dart';
5 import 'package:nested_scroll_views/material.dart'; 5 import 'package:nested_scroll_views/material.dart';
6 import 'package:wow_english/common/core/user_util.dart'; 6 import 'package:wow_english/common/core/user_util.dart';
7 -import 'package:wow_english/common/utils/action_with_music_controller.dart'; 7 +import 'package:wow_english/common/utils/click_with_music_controller.dart';
8 import 'package:wow_english/models/course_unit_entity.dart'; 8 import 'package:wow_english/models/course_unit_entity.dart';
9 import 'package:wow_english/pages/section/section_type.dart'; 9 import 'package:wow_english/pages/section/section_type.dart';
10 import 'package:wow_english/pages/section/widgets/section_item.dart'; 10 import 'package:wow_english/pages/section/widgets/section_item.dart';
@@ -15,6 +15,7 @@ import &#39;package:wow_english/utils/audio_player_util.dart&#39;; @@ -15,6 +15,7 @@ import &#39;package:wow_english/utils/audio_player_util.dart&#39;;
15 import 'package:wow_english/utils/toast_util.dart'; 15 import 'package:wow_english/utils/toast_util.dart';
16 16
17 import '../../models/course_section_entity.dart'; 17 import '../../models/course_section_entity.dart';
  18 +import '../../utils/log_util.dart';
18 import 'bloc/section_bloc.dart'; 19 import 'bloc/section_bloc.dart';
19 import 'courese_module_model.dart'; 20 import 'courese_module_model.dart';
20 21
@@ -56,30 +57,34 @@ class _SectionPageView extends StatelessWidget { @@ -56,30 +57,34 @@ class _SectionPageView extends StatelessWidget {
56 @override 57 @override
57 Widget build(BuildContext context) { 58 Widget build(BuildContext context) {
58 final bloc = BlocProvider.of<SectionBloc>(context); 59 final bloc = BlocProvider.of<SectionBloc>(context);
59 - final controller = ActionWithMusicController(); 60 + final clickController = ClickWithMusicController.instance;
60 return BlocListener<SectionBloc, SectionState>( 61 return BlocListener<SectionBloc, SectionState>(
61 listener: (context, state) async { 62 listener: (context, state) async {
62 if (state is RequestEnterClassState) { 63 if (state is RequestEnterClassState) {
63 - if (state.courseType == SectionType.song.value ||  
64 - state.courseType == SectionType.video.value) {  
65 - var title =  
66 - state.courseType == SectionType.song.value ? 'song' : 'video'; 64 + final courseType = state.courseType;
  65 + final courseLessonId = state.courseLessonId;
  66 + if (courseType == SectionType.song.value ||
  67 + courseType == SectionType.video.value) {
  68 + final title =
  69 + courseType == SectionType.song.value ? 'song' : 'video';
  70 + final AudioPlayerUtilType audioType;
67 71
68 - AudioPlayerUtilType audioType;  
69 ///儿歌/视频类型 72 ///儿歌/视频类型
70 - if (state.courseType == SectionType.song.value) { 73 + if (courseType == SectionType.song.value) {
71 audioType = AudioPlayerUtilType.musicTime; 74 audioType = AudioPlayerUtilType.musicTime;
72 } else { 75 } else {
73 audioType = AudioPlayerUtilType.videoTime; 76 audioType = AudioPlayerUtilType.videoTime;
74 } 77 }
75 78
76 - controller.playMusicAndPerformAction(context, audioType, () async { 79 + clickController.playMusicAndPerformAction(context, audioType,
  80 + () async {
77 ///播放音乐->调进入课程接口->跳转课程页面 81 ///播放音乐->调进入课程接口->跳转课程页面
78 - bloc.requestEnterClass(state.courseLessonId, () { 82 + await bloc.requestEnterClass(courseLessonId, () {
  83 + Log.d("WQF request finish");
79 pushNamed(AppRouteName.lookVideo, arguments: { 84 pushNamed(AppRouteName.lookVideo, arguments: {
80 'videoUrl': null, 85 'videoUrl': null,
81 'title': title, 86 'title': title,
82 - 'courseLessonId': state.courseLessonId, 87 + 'courseLessonId': courseLessonId,
83 'isTopic': true 88 'isTopic': true
84 }).then((value) { 89 }).then((value) {
85 if (value != null) { 90 if (value != null) {
@@ -93,50 +98,58 @@ class _SectionPageView extends StatelessWidget { @@ -93,50 +98,58 @@ class _SectionPageView extends StatelessWidget {
93 AudioPlayerUtil.getInstance() 98 AudioPlayerUtil.getInstance()
94 .playAudio(AudioPlayerUtilType.countWithMe); 99 .playAudio(AudioPlayerUtilType.countWithMe);
95 }); 100 });
  101 + }, onRequestEnterFailed: (error) {
  102 + Log.d("WQF requestEnterClass failed $error");
96 }); 103 });
97 }); 104 });
98 return; 105 return;
99 } 106 }
100 107
101 - if (state.courseType == SectionType.pictureBook.value) { 108 + if (courseType == SectionType.pictureBook.value) {
102 //绘本 109 //绘本
103 - controller.playMusicAndPerformAction( 110 + clickController.playMusicAndPerformAction(
104 context, AudioPlayerUtilType.readingTime, () async { 111 context, AudioPlayerUtilType.readingTime, () async {
105 - pushNamed(AppRouteName.reading,  
106 - arguments: {'courseLessonId': state.courseLessonId})  
107 - .then((value) {  
108 - if (value != null) {  
109 - Map<String, dynamic> dataMap = value as Map<String, dynamic>;  
110 - bloc.add(RequestEndClassEvent(  
111 - dataMap['courseLessonId']!,  
112 - dataMap['isCompleted'],  
113 - currentStep: dataMap['currentStep'],  
114 - autoNextSection: dataMap['nextSection'],  
115 - ));  
116 - AudioPlayerUtil.getInstance()  
117 - .playAudio(AudioPlayerUtilType.countWithMe);  
118 - } 112 + await bloc.requestEnterClass(courseLessonId, () {
  113 + pushNamed(AppRouteName.reading,
  114 + arguments: {'courseLessonId': courseLessonId})
  115 + .then((value) {
  116 + if (value != null) {
  117 + Map<String, dynamic> dataMap =
  118 + value as Map<String, dynamic>;
  119 + bloc.add(RequestEndClassEvent(
  120 + dataMap['courseLessonId']!,
  121 + dataMap['isCompleted'],
  122 + currentStep: dataMap['currentStep'],
  123 + autoNextSection: dataMap['nextSection'],
  124 + ));
  125 + AudioPlayerUtil.getInstance()
  126 + .playAudio(AudioPlayerUtilType.countWithMe);
  127 + }
  128 + });
119 }); 129 });
120 }); 130 });
121 return; 131 return;
122 } 132 }
123 133
124 - if (state.courseType == SectionType.practice.value) { 134 + if (courseType == SectionType.practice.value) {
125 //练习 135 //练习
126 - controller.playMusicAndPerformAction( 136 + clickController.playMusicAndPerformAction(
127 context, AudioPlayerUtilType.quizTime, () async { 137 context, AudioPlayerUtilType.quizTime, () async {
128 - pushNamed(AppRouteName.topicPic,  
129 - arguments: {'courseLessonId': state.courseLessonId})  
130 - .then((value) {  
131 - if (value != null) {  
132 - Map<String, dynamic> dataMap = value as Map<String, dynamic>;  
133 - bloc.add(RequestEndClassEvent(  
134 - dataMap['courseLessonId']!, dataMap['isCompleted'],  
135 - currentStep: dataMap['currentStep'],  
136 - autoNextSection: dataMap['nextSection']));  
137 - }  
138 - AudioPlayerUtil.getInstance()  
139 - .playAudio(AudioPlayerUtilType.countWithMe); 138 + await bloc.requestEnterClass(courseLessonId, () {
  139 + pushNamed(AppRouteName.topicPic,
  140 + arguments: {'courseLessonId': courseLessonId})
  141 + .then((value) {
  142 + if (value != null) {
  143 + Map<String, dynamic> dataMap =
  144 + value as Map<String, dynamic>;
  145 + bloc.add(RequestEndClassEvent(
  146 + dataMap['courseLessonId']!, dataMap['isCompleted'],
  147 + currentStep: dataMap['currentStep'],
  148 + autoNextSection: dataMap['nextSection']));
  149 + }
  150 + AudioPlayerUtil.getInstance()
  151 + .playAudio(AudioPlayerUtilType.countWithMe);
  152 + });
140 }); 153 });
141 }); 154 });
142 return; 155 return;