Commit 0493c1044f4ffedea1543ff82814cd3f8f08d9b1
1 parent
cd32eb01
feat:带音乐点击防抖函数优化
Showing
3 changed files
with
84 additions
and
58 deletions
lib/common/utils/action_with_music_controller.dart renamed to lib/common/utils/click_with_music_controller.dart
1 | +import 'dart:async'; | |
2 | + | |
1 | 3 | import 'package:flutter/material.dart'; |
2 | 4 | |
3 | 5 | import '../../utils/audio_player_util.dart'; |
6 | +import '../../utils/log_util.dart'; | |
4 | 7 | |
5 | 8 | /// action前播放音乐控制类,维护状态做防抖处理 |
6 | 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 | 18 | bool _isPlaying = false; |
17 | 19 | |
20 | + ///@param action 可以是同步函数也可以是异步函数 | |
18 | 21 | Future<void> playMusicAndPerformAction(BuildContext context, |
19 | - AudioPlayerUtilType audioType, Function action) async { | |
22 | + AudioPlayerUtilType audioType, FutureOr<void> Function() action) async { | |
20 | 23 | if (_isPlaying) return; |
21 | 24 | |
22 | 25 | _isPlaying = true; |
26 | + Log.d("WQF playMusicAndPerformAction playAudio begin"); | |
23 | 27 | |
24 | 28 | // Play the music |
25 | 29 | await AudioPlayerUtil.getInstance() |
26 | 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 | 42 | // void dispose() { | ... | ... |
lib/pages/section/bloc/section_bloc.dart
1 | +import 'dart:async'; | |
2 | + | |
1 | 3 | import 'package:flutter/cupertino.dart'; |
2 | 4 | import 'package:flutter/foundation.dart'; |
3 | 5 | import 'package:flutter/material.dart'; |
... | ... | @@ -57,7 +59,8 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
57 | 59 | on<RequestEnterClassEvent>(_onEnterClass); |
58 | 60 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); |
59 | 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<SectionEvent, SectionState> { |
87 | 90 | |
88 | 91 | ///进入课程接口请求函数封装 |
89 | 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 | 96 | await loading(() async { |
93 | 97 | try { |
94 | 98 | await ListenDao.enterClass(courseLessonId); |
95 | - onRequestEnterSuccess(); | |
99 | + await Future.sync(onRequestEnterSuccess); | |
96 | 100 | } catch (e) { |
97 | 101 | if (e is ApiException) { |
98 | - showToast(e.message ?? '请求失败,请检查网络连接'); | |
102 | + showToast('进入课堂失败 ${e.message}'); | |
99 | 103 | } else { |
100 | - showToast('$e'); | |
104 | + showToast('进入课堂失败 $e'); | |
101 | 105 | } |
102 | 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 'package:flutter_bloc/flutter_bloc.dart'; |
4 | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
5 | 5 | import 'package:nested_scroll_views/material.dart'; |
6 | 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 | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
9 | 9 | import 'package:wow_english/pages/section/section_type.dart'; |
10 | 10 | import 'package:wow_english/pages/section/widgets/section_item.dart'; |
... | ... | @@ -15,6 +15,7 @@ import 'package:wow_english/utils/audio_player_util.dart'; |
15 | 15 | import 'package:wow_english/utils/toast_util.dart'; |
16 | 16 | |
17 | 17 | import '../../models/course_section_entity.dart'; |
18 | +import '../../utils/log_util.dart'; | |
18 | 19 | import 'bloc/section_bloc.dart'; |
19 | 20 | import 'courese_module_model.dart'; |
20 | 21 | |
... | ... | @@ -56,30 +57,34 @@ class _SectionPageView extends StatelessWidget { |
56 | 57 | @override |
57 | 58 | Widget build(BuildContext context) { |
58 | 59 | final bloc = BlocProvider.of<SectionBloc>(context); |
59 | - final controller = ActionWithMusicController(); | |
60 | + final clickController = ClickWithMusicController.instance; | |
60 | 61 | return BlocListener<SectionBloc, SectionState>( |
61 | 62 | listener: (context, state) async { |
62 | 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 | 74 | audioType = AudioPlayerUtilType.musicTime; |
72 | 75 | } else { |
73 | 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 | 84 | pushNamed(AppRouteName.lookVideo, arguments: { |
80 | 85 | 'videoUrl': null, |
81 | 86 | 'title': title, |
82 | - 'courseLessonId': state.courseLessonId, | |
87 | + 'courseLessonId': courseLessonId, | |
83 | 88 | 'isTopic': true |
84 | 89 | }).then((value) { |
85 | 90 | if (value != null) { |
... | ... | @@ -93,50 +98,58 @@ class _SectionPageView extends StatelessWidget { |
93 | 98 | AudioPlayerUtil.getInstance() |
94 | 99 | .playAudio(AudioPlayerUtilType.countWithMe); |
95 | 100 | }); |
101 | + }, onRequestEnterFailed: (error) { | |
102 | + Log.d("WQF requestEnterClass failed $error"); | |
96 | 103 | }); |
97 | 104 | }); |
98 | 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 | 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 | 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 | 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 | 155 | return; | ... | ... |