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 | 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<SectionEvent, SectionState> { | @@ -57,7 +59,8 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
| 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<SectionEvent, SectionState> { | @@ -87,20 +90,21 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
| 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 'package:flutter_bloc/flutter_bloc.dart'; | @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| 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 'package:wow_english/utils/audio_player_util.dart'; | @@ -15,6 +15,7 @@ import 'package:wow_english/utils/audio_player_util.dart'; | ||
| 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; |