diff --git a/assets/images/micro_phone.gif b/assets/images/micro_phone.gif new file mode 100644 index 0000000..d1da100 --- /dev/null +++ b/assets/images/micro_phone.gif diff --git a/assets/images/reade_answer.gif b/assets/images/reade_answer.gif new file mode 100644 index 0000000..39f882f --- /dev/null +++ b/assets/images/reade_answer.gif diff --git a/ios/Runner/XSMessageMehtodChannel.swift b/ios/Runner/XSMessageMehtodChannel.swift index 700427b..a4a5c57 100644 --- a/ios/Runner/XSMessageMehtodChannel.swift +++ b/ios/Runner/XSMessageMehtodChannel.swift @@ -39,7 +39,7 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { } //开始评测 - func evaluateVioce(dict:Dictionary) { + func evaluateVoice(dict:Dictionary) { let text = dict["word"] as? String ?? "" let type = dict["type"] as? String ?? "0" let userId = dict["userId"] as? String ?? "guest" @@ -50,6 +50,7 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { } else { config.oralType = .sentence } + config.oralType = .kidSent config.userId = userId SSOralEvaluatingManager.share().startEvaluateOral(with: config) } @@ -59,8 +60,8 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { self.setEvaluateConfig(dict:call.arguments as! Dictionary) return } - if (call.method == "starVoice") { - self.evaluateVioce(dict: call.arguments as! Dictionary) + if (call.method == "startVoice") { + self.evaluateVoice(dict: call.arguments as! Dictionary) return } diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index eaf8c3c..e3a51d9 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:wow_english/common/request/dao/home_dao.dart'; import 'package:wow_english/common/request/exception.dart'; import 'package:wow_english/models/course_entity.dart'; +import 'package:wow_english/common/request/dao/listen_dao.dart'; +import 'package:wow_english/models/course_process_entity.dart'; // import 'package:wow_english/models/course_lesson_entity.dart'; import 'package:wow_english/utils/loading.dart'; import 'package:wow_english/utils/toast_util.dart'; @@ -18,8 +20,13 @@ class HomeBloc extends Bloc { CourseEntity? get modelData => _modelData; + CourseProcessEntity? _processEntity; + + CourseProcessEntity? get processEntity => _processEntity; + HomeBloc(this.moduleId) : super(HomeInitial()) { on(_requestData); + on(_requestVideoLesson); } void _requestData(RequestDataEvent event, Emitter emitter) async { @@ -34,4 +41,17 @@ class HomeBloc extends Bloc { } } } + + void _requestVideoLesson(RequestVideoLessonEvent event, Emitter emitter) async { + try { + await loading(() async { + _processEntity = await ListenDao.process(event.courseLessonId); + emitter(RequestVideoLessonState(event.courseType)); + }); + } catch (e) { + if (e is ApiException) { + showToast(e.message??'请求失败,请检查网络连接'); + } + } + } } diff --git a/lib/pages/home/bloc/home_event.dart b/lib/pages/home/bloc/home_event.dart index 7717d15..61e6317 100644 --- a/lib/pages/home/bloc/home_event.dart +++ b/lib/pages/home/bloc/home_event.dart @@ -4,3 +4,10 @@ part of 'home_bloc.dart'; abstract class HomeEvent {} class RequestDataEvent extends HomeEvent {} + +///获取视频课程内容 +class RequestVideoLessonEvent extends HomeEvent { + final String courseLessonId; + final int courseType; + RequestVideoLessonEvent(this.courseLessonId, this.courseType); +} diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 8f83f67..e403530 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -6,3 +6,8 @@ abstract class HomeState {} class HomeInitial extends HomeState {} class HomeDataLoadState extends HomeState {} + +class RequestVideoLessonState extends HomeState { + final int type; + RequestVideoLessonState(this.type); +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 98251e5..1f8609a 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -45,8 +45,30 @@ class _HomePageView extends StatelessWidget { @override Widget build(BuildContext context) { + final bloc = BlocProvider.of(context); return BlocListener( - listener: (context, state) {}, + listener: (context, state) { + if (state is RequestVideoLessonState) { + final videoUrl = bloc.processEntity?.videos?.videoUrl??''; + var title = ''; + if (state.type == 1) { + title = 'song'; + } + + if (state.type == 2) { + title = 'video'; + } + + if (state.type == 5) { + title = 'bonus'; + } + debugPrint(videoUrl); + if (videoUrl.isEmpty) { + return; + } + Navigator.of(context).pushNamed(AppRouteName.lookVideo,arguments: {'videoUrl':videoUrl,'title':title}); + } + }, child: _homeView(), ); } @@ -76,9 +98,10 @@ class _HomePageView extends StatelessWidget { return GestureDetector( onTap: () { if (data!.lock!) { + showToast('当前课程暂未解锁'); return; } - showToast('点击事件'); + bloc.add(RequestVideoLessonEvent(data.id!,data.courseType!)); }, child: HomeBoundsItem( imageUrl: data?.coverUrl, @@ -87,17 +110,22 @@ class _HomePageView extends StatelessWidget { } else { return GestureDetector( onTap: () { + debugPrint('>>>>>>>类型${data?.courseType}'); if (data!.lock!) { + showToast('当前课程暂未解锁'); return; } - if (data!.courseType == 4) {//绘本 + if (data.courseType == 4) {//绘本 return; } if (data.courseType == 3) {//练习 - Navigator.of(context).pushNamed(AppRouteName.topicPic,arguments: {'courseLessonId':data!.id}); + Navigator.of(context).pushNamed(AppRouteName.topicPic,arguments: {'courseLessonId':data.id!}); return; } + + //儿歌/看视频 + bloc.add(RequestVideoLessonEvent(data.id!,data.courseType!)); }, child: HomeVideoItem( lessons: data, diff --git a/lib/pages/home/widgets/home_tab_header_widget.dart b/lib/pages/home/widgets/home_tab_header_widget.dart index 824e390..dc2f732 100644 --- a/lib/pages/home/widgets/home_tab_header_widget.dart +++ b/lib/pages/home/widgets/home_tab_header_widget.dart @@ -71,7 +71,7 @@ class HomeTabHeaderWidget extends StatelessWidget { 20.horizontalSpace, const Expanded( child: Text( - 'learn wow!yellow', + 'learn wow', textAlign: TextAlign.left, style: TextStyle(color: Colors.white, fontSize: 30.0), )), diff --git a/lib/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index 160eb98..252d86b 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -8,10 +8,22 @@ import 'package:wow_english/common/request/dao/listen_dao.dart'; import 'package:wow_english/common/request/exception.dart'; import 'package:wow_english/models/course_process_entity.dart'; import 'package:wow_english/utils/loading.dart'; +import 'package:wow_english/utils/toast_util.dart'; part 'topic_picture_event.dart'; part 'topic_picture_state.dart'; +enum VoicePlayState { + ///未知 + unKnow, + ///播放中 + playing, + ///播放完成 + completed, + ///播放终止 + stop +} + class TopicPictureBloc extends Bloc { final PageController pageController; @@ -27,6 +39,9 @@ class TopicPictureBloc extends Bloc { ///正在评测 bool _isVoicing = false; + ///正在播放音频 + VoicePlayState _voicePlayState = VoicePlayState.unKnow; + CourseProcessEntity? get entity => _entity; int get currentPage => _currentPage + 1; @@ -35,29 +50,46 @@ class TopicPictureBloc extends Bloc { bool get isVoicing => _isVoicing; + VoicePlayState get voicePlayState => _voicePlayState; + late MethodChannel methodChannel; late AudioPlayer audioPlayer; TopicPictureBloc(this.pageController, this.courseLessonId) : super(TopicPictureInitial()) { on(_pageControllerChange); + on(_voicePlayStateChange); + on(_voiceXsResult); + on(_initVoiceSdk); on(_selectItemLoad); on(_requestData); on(_voiceXsTest); - on(_voiceXsResult); - on(_initVoiceSdk); - on((event, emit) { + on(_voiceXsStop); + on(_voicePlay); + on((event, emit) { //音频播放器 audioPlayer = AudioPlayer(); - audioPlayer.onPlayerStateChanged.listen((event) { + audioPlayer.onPlayerStateChanged.listen((event) async { + debugPrint('播放状态变化'); if (event == PlayerState.completed) { - if (kDebugMode) { - print('播放完成'); + debugPrint('播放完成'); + _voicePlayState = VoicePlayState.completed; + } + if (event == PlayerState.stopped) { + debugPrint('播放结束'); + _voicePlayState = VoicePlayState.stop; + } - }} + if (event == PlayerState.playing) { + debugPrint('正在播放中'); + _voicePlayState = VoicePlayState.playing; + } + if(isClosed) { + return; + } + add(VoicePlayStateChangeEvent()); }); - methodChannel = const MethodChannel('wow_english/sing_sound_method_channely'); methodChannel.setMethodCallHandler((call) async { if (call.method == 'voiceResult') {//评测结果 @@ -88,8 +120,9 @@ class TopicPictureBloc extends Bloc { } @override - Future close() { + Future close(){ pageController.dispose(); + audioPlayer.release(); audioPlayer.dispose(); return super.close(); } @@ -103,30 +136,26 @@ class TopicPictureBloc extends Bloc { }); } catch (e) { if (e is ApiException) { - EasyLoading.showToast(e.message??'请求失败,请检查网络连接'); + showToast(e.message??'请求失败,请检查网络连接'); } } } - ///初始化SDK - _initVoiceSdk(XSVoiceInitEvent event,Emitter emitter) async { - methodChannel.invokeMethod('initVoiceSdk',event.data); - } - ///页面切换 void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter emitter) async { _currentPage = event.pageIndex; + if (voicePlayState == VoicePlayState.playing) { + await audioPlayer.stop(); + } final topics = _entity?.topics?[_currentPage]; - if (topics?.type == 1 || topics?.type == 2 || topics?.type == 5) { - audioPlayer.stop(); + if (topics?.type != 3 && topics?.type != 4) { if (topics?.audioUrl != null) { final urlStr = topics?.audioUrl??''; if (urlStr.isNotEmpty) { - audioPlayer.play(UrlSource(urlStr)); + debugPrint(urlStr); + await audioPlayer.play(UrlSource(urlStr)); } } - } else { - audioPlayer.stop(); } _selectItem = -1; emitter(CurrentPageIndexState()); @@ -135,12 +164,24 @@ class TopicPictureBloc extends Bloc { ///选择 void _selectItemLoad(SelectItemEvent event,Emitter emitter) async { _selectItem = event.selectIndex; + CourseProcessTopics? topics = _entity?.topics?[_currentPage]; + CourseProcessTopicsTopicAnswerList? answerList = topics?.topicAnswerList?[_selectItem]; + if (answerList?.correct == 0) { + showToast('继续加油哦',duration: const Duration(seconds: 2)); + } else { + showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); + } emitter(SelectItemChangeState()); } + ///初始化SDK + _initVoiceSdk(XSVoiceInitEvent event,Emitter emitter) async { + methodChannel.invokeMethod('initVoiceSdk',event.data); + } + ///先声测试 void _voiceXsTest(XSVoiceTestEvent event,Emitter emitter) async { - EasyLoading.show(status: '录音中....'); + audioPlayer.stop(); methodChannel.invokeMethod( 'startVoice', {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} @@ -149,16 +190,36 @@ class TopicPictureBloc extends Bloc { emitter(XSVoiceTestState()); } + ///终止评测 + void _voiceXsStop(XSVoiceStopEvent event,Emitter emitter) async { + methodChannel.invokeMethod('stopVoice'); + } + + ///先声评测结果 void _voiceXsResult(XSVoiceResultEvent event,Emitter emitter) async { final Map args = event.message as Map; final result = args['result'] as String; if (result == '1') { final overall = args['overall'].toString(); - EasyLoading.showToast('测评成功,分数是$overall',duration: const Duration(seconds: 10)); + showToast('测评成功,分数是$overall',duration: const Duration(seconds: 5)); } else { - EasyLoading.showToast('测评失败',duration: const Duration(seconds: 10)); + showToast('测评失败',duration: const Duration(seconds: 5)); } _isVoicing = false; emitter(XSVoiceTestState()); } + + void _voicePlayStateChange(VoicePlayStateChangeEvent event,Emitter emitter) async { + emitter(VoicePlayStateChange()); + } + + void _voicePlay(VoicePlayEvent event,Emitter emitter) async { + if (voicePlayState == VoicePlayState.playing) { + await audioPlayer.stop(); + return; + } + final topics = _entity?.topics?[_currentPage]; + final urlStr = topics?.audioUrl??''; + await audioPlayer.play(UrlSource(urlStr)); + } } diff --git a/lib/pages/practice/bloc/topic_picture_event.dart b/lib/pages/practice/bloc/topic_picture_event.dart index ec1650e..f69ef33 100644 --- a/lib/pages/practice/bloc/topic_picture_event.dart +++ b/lib/pages/practice/bloc/topic_picture_event.dart @@ -3,6 +3,8 @@ part of 'topic_picture_bloc.dart'; @immutable abstract class TopicPictureEvent {} +class InitBlocEvent extends TopicPictureEvent {} + class RequestDataEvent extends TopicPictureEvent {} ///初始化先声SDK @@ -11,13 +13,7 @@ class XSVoiceInitEvent extends TopicPictureEvent { XSVoiceInitEvent(this.data); } -///评测结果 -class XSVoiceResultEvent extends TopicPictureEvent { - final dynamic message; - XSVoiceResultEvent(this.message); -} - -///先声测试 +///开始评测 class XSVoiceTestEvent extends TopicPictureEvent { final String testWord; final String type; @@ -25,12 +21,32 @@ class XSVoiceTestEvent extends TopicPictureEvent { XSVoiceTestEvent(this.testWord,this.type,this.userId); } +///终止评测 +class XSVoiceStopEvent extends TopicPictureEvent {} + +///评测结果 +class XSVoiceResultEvent extends TopicPictureEvent { + final dynamic message; + XSVoiceResultEvent(this.message); +} + +///音频播放状态变化 +class VoicePlayStateChangeEvent extends TopicPictureEvent {} + +///页面切换 class CurrentPageIndexChangeEvent extends TopicPictureEvent { final int pageIndex; CurrentPageIndexChangeEvent(this.pageIndex); } +///选择答案 class SelectItemEvent extends TopicPictureEvent { final int selectIndex; SelectItemEvent(this.selectIndex); -} \ No newline at end of file +} + +///音频播放事件 +class VoicePlayChangeEvent extends TopicPictureEvent {} + +///播放音乐 +class VoicePlayEvent extends TopicPictureEvent {} \ No newline at end of file diff --git a/lib/pages/practice/bloc/topic_picture_state.dart b/lib/pages/practice/bloc/topic_picture_state.dart index 3d75788..96d7381 100644 --- a/lib/pages/practice/bloc/topic_picture_state.dart +++ b/lib/pages/practice/bloc/topic_picture_state.dart @@ -3,12 +3,14 @@ part of 'topic_picture_bloc.dart'; @immutable abstract class TopicPictureState {} +class TopicPictureInitial extends TopicPictureState {} + class RequestDataState extends TopicPictureState {} class XSVoiceTestState extends TopicPictureState {} -class TopicPictureInitial extends TopicPictureState {} - class CurrentPageIndexState extends TopicPictureState {} class SelectItemChangeState extends TopicPictureState {} + +class VoicePlayStateChange extends TopicPictureState {} diff --git a/lib/pages/practice/topic_picture_page.dart b/lib/pages/practice/topic_picture_page.dart index f79ce4d..8c11e22 100644 --- a/lib/pages/practice/topic_picture_page.dart +++ b/lib/pages/practice/topic_picture_page.dart @@ -5,11 +5,12 @@ import 'package:wow_english/common/core/user_util.dart'; import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/widgets/ow_image_widget.dart'; import 'package:wow_english/models/course_process_entity.dart'; +import 'package:wow_english/utils/toast_util.dart'; import 'bloc/topic_picture_bloc.dart'; import 'widgets/practice_header_widget.dart'; -class TopicPicturePage extends StatelessWidget { +class TopicPicturePage extends StatelessWidget { const TopicPicturePage({super.key, this.courseLessonId}); final String? courseLessonId; @@ -18,9 +19,10 @@ class TopicPicturePage extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => TopicPictureBloc( - PageController(), - courseLessonId??'', + PageController(), + courseLessonId??'', ) + ..add(InitBlocEvent()) ..add(RequestDataEvent()) ..add(XSVoiceInitEvent( { @@ -258,13 +260,23 @@ class _TopicPicturePage extends StatelessWidget { ///听音选图 Widget _pageViewVoicePictureItemWidget(CourseProcessTopics? topics) => BlocBuilder( builder: (context, state){ + final bloc = BlocProvider.of(context); return SafeArea( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), + GestureDetector( + onTap: () { + bloc.add(VoicePlayEvent()); + }, + child: Image.asset( + bloc.voicePlayState == VoicePlayState.playing?'reade_answer'.assetGif:'voice'.assetPng, + height: 33.h, + width: 30.w, + ), + ), 10.horizontalSpace, Text( topics?.word??'', @@ -331,10 +343,20 @@ class _TopicPicturePage extends StatelessWidget { ///听音选字 Widget _pageViewVoiceWordItemWidget(CourseProcessTopics? topics) => BlocBuilder( builder: (context, state){ + final bloc = BlocProvider.of(context); return SafeArea( child: Column( children: [ - Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), + GestureDetector( + onTap: () { + bloc.add(VoicePlayEvent()); + }, + child: Image.asset( + bloc.voicePlayState == VoicePlayState.playing?'reade_answer'.assetGif:'voice'.assetPng, + height: 33.h, + width: 30.w + ) + ), 26.verticalSpace, SizedBox( width: 163.w * (topics?.topicAnswerList?.length??0), @@ -426,31 +448,45 @@ class _TopicPicturePage extends StatelessWidget { Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Row( - children: [ - Image.asset( - 'voice'.assetPng, - height: 52.h, - width: 46.w, - ), - 10.horizontalSpace, - Text(topics?.word??'') - ], + GestureDetector( + onTap: () { + if (bloc.isVoicing) { + showToast('正在录音,不能终止'); + return; + } + bloc.add(VoicePlayEvent()); + }, + child: Row( + children: [ + Image.asset( + bloc.voicePlayState == VoicePlayState.playing?'reade_answer'.assetGif:'voice'.assetPng, + height: 52.h, + width: 46.w, + ), + 10.horizontalSpace, + Text(topics?.word??'') + ], + ), ), 70.verticalSpace, GestureDetector( onTap: () { + if (bloc.voicePlayState == VoicePlayState.playing) { + showToast('正在播放音屏,不能终止'); + return; + } + if (bloc.isVoicing) { return; } - if (topics?.type == 5) { + if (topics?.type == 5 || topics?.type == 7) { bloc.add(XSVoiceTestEvent(topics?.keyWord??'', '0',UserUtil.getUser()!.id.toString())); } else { bloc.add(XSVoiceTestEvent(topics?.word??'', '0',UserUtil.getUser()!.id.toString())); } }, child: Image.asset( - 'micro_phone'.assetPng, + bloc.isVoicing?'micro_phone'.assetGif:'micro_phone'.assetPng, height: 75.w, width: 75.w, ), diff --git a/lib/pages/reading/bloc/reading_bloc.dart b/lib/pages/reading/bloc/reading_bloc.dart index 01f420e..c36aac6 100644 --- a/lib/pages/reading/bloc/reading_bloc.dart +++ b/lib/pages/reading/bloc/reading_bloc.dart @@ -125,7 +125,7 @@ class ReadingPageBloc extends Bloc { if (readingData?.audioUrl != null) { final urlStr = audioUrl ?? readingData?.audioUrl ?? ''; if (urlStr.isNotEmpty) { - audioPlayer.play(UrlSource(urlStr)); + audioPlayer.play(UrlSource(urlStr)); } } } diff --git a/lib/pages/video/lookvideo/widgets/video_opera_widget.dart b/lib/pages/video/lookvideo/widgets/video_opera_widget.dart index 62c2669..cea5464 100644 --- a/lib/pages/video/lookvideo/widgets/video_opera_widget.dart +++ b/lib/pages/video/lookvideo/widgets/video_opera_widget.dart @@ -94,6 +94,7 @@ class _VideoOperaWidgetState extends State { child: Text( widget.title, textAlign: TextAlign.center, + overflow: TextOverflow.clip, style: TextStyle( fontSize: 20.sp, color: const Color(0xFF333333),