diff --git a/assets/images/reade_answer.gif b/assets/images/reade_answer.gif old mode 100755 new mode 100644 index 62eb138..be6c871 --- a/assets/images/reade_answer.gif +++ b/assets/images/reade_answer.gif diff --git a/assets/images/voice.png b/assets/images/voice.png index cd5f697..d186c69 100644 --- a/assets/images/voice.png +++ b/assets/images/voice.png diff --git a/assets/sounds/count_with_me_instrumental.mp3 b/assets/sounds/count_with_me_instrumental.mp3 new file mode 100644 index 0000000..483dbc8 --- /dev/null +++ b/assets/sounds/count_with_me_instrumental.mp3 diff --git a/assets/sounds/in_my_tummy_instrumental.mp3 b/assets/sounds/in_my_tummy_instrumental.mp3 new file mode 100644 index 0000000..0edc830 --- /dev/null +++ b/assets/sounds/in_my_tummy_instrumental.mp3 diff --git a/assets/sounds/touch_instrumental.mp3 b/assets/sounds/touch_instrumental.mp3 new file mode 100644 index 0000000..5b224cc --- /dev/null +++ b/assets/sounds/touch_instrumental.mp3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c50f249..45bb20f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -2327,7 +2327,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = T8P9KW8GWH; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -2671,7 +2671,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = T8P9KW8GWH; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -2876,7 +2876,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = T8P9KW8GWH; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/lib/pages/home/bloc.dart b/lib/pages/home/bloc.dart index 3249345..7fc475b 100644 --- a/lib/pages/home/bloc.dart +++ b/lib/pages/home/bloc.dart @@ -16,9 +16,25 @@ class HomeBloc extends Bloc { } bool exchangeResult = false; + late AudioPlayer audioPlayer; + late AudioPlayer studyPlayer; + late AudioPlayer gamePlayer; + @override + Future close() { + audioPlayer.release(); + audioPlayer.dispose(); + studyPlayer.release(); + studyPlayer.dispose(); + gamePlayer.release(); + gamePlayer.dispose(); + return super.close(); + } void _init(InitEvent event, Emitter emit) async { - AudioPlayer().play(AssetSource('welcome_to_wow'.assetMp3)); + audioPlayer = AudioPlayer(playerId: 'audio'); + gamePlayer = AudioPlayer(playerId: 'game'); + studyPlayer = AudioPlayer(playerId: 'study'); + audioPlayer.play(AssetSource('welcome_to_wow'.assetMp3)); await _checkUpdate(emit); } diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index ede914a..5dfa35b 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -71,7 +71,7 @@ class _HomePageView extends StatelessWidget { child: GestureDetector( onTap: () { _checkPermission(() { - AudioPlayer() + bloc.studyPlayer .play(AssetSource('class_time'.assetMp3)); pushNamed(AppRouteName.courseUnit) .then((value) => { @@ -136,7 +136,7 @@ class _HomePageView extends StatelessWidget { return GestureDetector( onTap: () { _checkPermission(() { - AudioPlayer().play( + bloc.gamePlayer.play( AssetSource('game_time'.assetMp3)); pushNamed(AppRouteName.games); }, bloc); diff --git a/lib/pages/reading/reading_page.dart b/lib/pages/reading/reading_page.dart index ffbb424..cf13c4b 100644 --- a/lib/pages/reading/reading_page.dart +++ b/lib/pages/reading/reading_page.dart @@ -155,6 +155,7 @@ class _ReadingPage extends StatelessWidget { margin: EdgeInsets.symmetric(horizontal: 10.w), child: Row( children: [ + 5.horizontalSpace, GestureDetector( onTap: () { if (bloc.isRecording) { diff --git a/lib/pages/section/bloc/section_bloc.dart b/lib/pages/section/bloc/section_bloc.dart index 0c1d94c..77ed636 100644 --- a/lib/pages/section/bloc/section_bloc.dart +++ b/lib/pages/section/bloc/section_bloc.dart @@ -1,8 +1,10 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/request/dao/lesson_dao.dart'; import 'package:wow_english/common/request/exception.dart'; import 'package:wow_english/common/request/dao/listen_dao.dart'; @@ -43,6 +45,8 @@ class SectionBloc extends Bloc { ///单元列表是否有刷新,有的话返回上一页时通知其刷新接口数据 bool courseUnitEntityChanged = false; + late AudioPlayer audioPlayer; // 点击播放器 + late AudioPlayer backgroundPlayer; // 背景播放器 ///courseUnitId与课程环节列表的映射 final Map?> _courseSectionDatasMap = {}; @@ -61,16 +65,48 @@ class SectionBloc extends Bloc { on(_requestEnterClass); on(_requestVideoLesson); on(_pageControllerChange); + on(_init); + on((event, emit) { + // audioPlayer = AudioPlayer(playerId: 'section'); + // backgroundPlayer = AudioPlayer(playerId: 'back'); + // backgroundPlayer.onPlayerStateChanged.listen((event) async { + // if (event == PlayerState.completed) { + // debugPrint('播放结束'); + // backgroundPlayer + // .play(AssetSource('count_with_me_instrumental'.assetMp3)); + // } + // }); + // audioPlayer.onPlayerStateChanged.listen((event) async { + // if (event == PlayerState.completed) { + // debugPrint('播放结束'); + // } + // }); + }); + } + @override + Future close() { + audioPlayer.release(); + audioPlayer.dispose(); + backgroundPlayer.release(); + backgroundPlayer.dispose(); + return super.close(); + } + + void _init(InitEvent event, Emitter emit) async { + audioPlayer = AudioPlayer(playerId: 'section'); + // backgroundPlayer = AudioPlayer(playerId: 'back'); + // backgroundPlayer.play(AssetSource('count_with_me_instrumental'.assetMp3)); } void _requestSectionsData( RequestDataEvent event, Emitter emitter) async { try { await loading(() async { - List? courseSectionEntities = await LessonDao.courseSection(courseUnitId: event.courseUnitId); + List? courseSectionEntities = + await LessonDao.courseSection(courseUnitId: event.courseUnitId); if (courseSectionEntities != null) { _courseSectionDatasMap[event.courseUnitId] = - await LessonDao.courseSection(courseUnitId: event.courseUnitId); + await LessonDao.courseSection(courseUnitId: event.courseUnitId); emitter(LessonDataLoadState()); } }); @@ -224,7 +260,8 @@ class SectionBloc extends Bloc { ///查找当前unit的下一个section CourseSectionEntity? nextCourseSectionEntity = findCourseSectionBySort(curSectionSort + 1); - return checkCourseSectionLocked(courseLessonId, nextCourseSectionEntity, emitter); + return checkCourseSectionLocked( + courseLessonId, nextCourseSectionEntity, emitter); } catch (e) { if (e is ApiException) { showToast(e.message.toString()); @@ -234,7 +271,9 @@ class SectionBloc extends Bloc { } ///检查section是否锁定 - Future checkCourseSectionLocked(int courseLessonId, CourseSectionEntity? courseSectionEntity, + Future checkCourseSectionLocked( + int courseLessonId, + CourseSectionEntity? courseSectionEntity, Emitter emitter) async { if (courseSectionEntity != null) { if (courseSectionEntity.lock == false) { @@ -243,15 +282,15 @@ class SectionBloc extends Bloc { } else { ///如果section锁了,请求当前unit下的section数据,查询解锁状态 int courseUnitId = courseSectionEntity.courseUnitId; - CourseSectionEntity? result = await loading(() async { + CourseSectionEntity? result = await loading(() async { List? tempSectionEntities = - await LessonDao.courseSection(courseUnitId: courseUnitId); + await LessonDao.courseSection(courseUnitId: courseUnitId); if (tempSectionEntities != null) { _courseSectionDatasMap[courseUnitId] = tempSectionEntities; emitter(LessonDataLoadState()); } courseSectionEntity = tempSectionEntities?.firstWhereOrNull( - (element) => element.id == courseSectionEntity?.id); + (element) => element.id == courseSectionEntity?.id); if (courseSectionEntity?.lock == false) { ///刷新后的数据如果解锁了,直接返回 return courseSectionEntity; @@ -270,18 +309,20 @@ class SectionBloc extends Bloc { if (curCourseUnitDetail != null) { ///再根据当前unit的sortOrder找出下一个unit CourseUnitDetail? nextCourseUnitDetail = - _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) => - element.sortOrder == (curCourseUnitDetail.sortOrder! + 1)); + _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) => + element.sortOrder == (curCourseUnitDetail.sortOrder! + 1)); if (nextCourseUnitDetail != null) { if (nextCourseUnitDetail.lock == true) { ///如果下一个unit是锁定状态,请求数据刷新查询解锁状态 CourseSectionEntity? result = await loading(() async { - CourseUnitEntity? newCourseUnitEntity = await LessonDao.courseUnit( - _courseUnitEntity.nowCourseModuleId); + CourseUnitEntity? newCourseUnitEntity = + await LessonDao.courseUnit( + _courseUnitEntity.nowCourseModuleId); ///拿到重新获取到的unit后,再次判断是否解锁 - nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList?.firstWhereOrNull( + nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList + ?.firstWhereOrNull( (element) => element.id == nextCourseUnitDetail?.id); if (nextCourseUnitDetail?.lock == false) { ///解锁状态从锁定到解锁,覆盖原unit数据并刷新ui @@ -289,7 +330,8 @@ class SectionBloc extends Bloc { courseUnitEntityChanged = true; emitter(LessonDataLoadState()); - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail!.id!, emitter); + return checkCourseSectionLockedOfNextUnit( + courseLessonId, nextCourseUnitDetail!.id!, emitter); } else { showToast('下个单元课程还没解锁哦'); @@ -299,10 +341,12 @@ class SectionBloc extends Bloc { }); return result; } else { - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail.id!, emitter); + return checkCourseSectionLockedOfNextUnit( + courseLessonId, nextCourseUnitDetail.id!, emitter); } } else { showToast("恭喜你,本阶段学到顶啦"); + ///最后一个unit了 return null; } @@ -314,13 +358,16 @@ class SectionBloc extends Bloc { } ///检查下一个unit的(第一个)section - Future checkCourseSectionLockedOfNextUnit(int courseLessonId, int nextCourseUnitDetailId, + Future checkCourseSectionLockedOfNextUnit( + int courseLessonId, + int nextCourseUnitDetailId, Emitter emitter) async { - CourseSectionEntity? firstSectionNextUnit = await getFirstSectionByUnitId( - nextCourseUnitDetailId, emitter); + CourseSectionEntity? firstSectionNextUnit = + await getFirstSectionByUnitId(nextCourseUnitDetailId, emitter); if (firstSectionNextUnit != null) { ///下个unit的第一个section如果不为空,再次检查是否锁定 - CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(courseLessonId, firstSectionNextUnit, emitter); + CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked( + courseLessonId, firstSectionNextUnit, emitter); if (courseSectionEntity != null) { ///只有是下一unit的第一个section并且解锁了,才跳转 _pageController.nextPage( diff --git a/lib/pages/section/bloc/section_event.dart b/lib/pages/section/bloc/section_event.dart index e9fa3ad..0aecf86 100644 --- a/lib/pages/section/bloc/section_event.dart +++ b/lib/pages/section/bloc/section_event.dart @@ -9,6 +9,8 @@ class RequestDataEvent extends SectionEvent { RequestDataEvent(this.courseUnitId); } +class InitEvent extends SectionEvent {} + ///获取视频课程内容 class RequestVideoLessonEvent extends SectionEvent { final String courseLessonId; @@ -53,3 +55,5 @@ class CurrentUnitIndexChangeEvent extends SectionEvent { CurrentUnitIndexChangeEvent(this.unitIndex); } + +class InitBlocEvent extends SectionEvent {} diff --git a/lib/pages/section/bloc/section_state.dart b/lib/pages/section/bloc/section_state.dart index 47aec56..b598397 100644 --- a/lib/pages/section/bloc/section_state.dart +++ b/lib/pages/section/bloc/section_state.dart @@ -22,3 +22,5 @@ class RequestEnterClassState extends SectionState { } class CurrentPageIndexState extends SectionState {} + +class VoicePlayChangeState extends SectionState {} diff --git a/lib/pages/section/section_page.dart b/lib/pages/section/section_page.dart index 27b4a53..eb801bc 100644 --- a/lib/pages/section/section_page.dart +++ b/lib/pages/section/section_page.dart @@ -39,7 +39,8 @@ class SectionPage extends StatelessWidget { initialPage, PageController(initialPage: initialPage), ScrollController(), - ScrollController()), + ScrollController()) + ..add(InitEvent()), //为了触发指示器进入后计算位置 // ..add(CurrentUnitIndexChangeEvent(initialPage)), child: _SectionPageView(context), @@ -88,57 +89,75 @@ class _SectionPageView extends StatelessWidget { currentTime: dataMap['currentTime'], autoNextSection: dataMap['nextSection'])); } + if (bloc.backgroundPlayer.state == PlayerState.paused) { + bloc.backgroundPlayer.resume(); + } }); return; } if (state is RequestEnterClassState) { + bloc.backgroundPlayer.pause(); if (state.courseType != SectionType.practice.value && state.courseType != SectionType.pictureBook.value) { ///视频类型 ///获取视频课程内容 if (state.courseType == 1) { - AudioPlayer().play(AssetSource('music_time'.assetMp3)); + bloc.audioPlayer.play(AssetSource('music_time'.assetMp3)); } else { - AudioPlayer().play(AssetSource('video_time'.assetMp3)); + bloc.audioPlayer.play(AssetSource('video_time'.assetMp3)); } - bloc.add(RequestVideoLessonEvent( - state.courseLessonId, state.courseType)); + Future.delayed(const Duration(seconds: 1), () { + bloc.add(RequestVideoLessonEvent( + state.courseLessonId, state.courseType)); + }); + return; } if (state.courseType == SectionType.pictureBook.value) { - AudioPlayer().play(AssetSource('reading_time'.assetMp3)); - //绘本 - pushNamed(AppRouteName.reading, - arguments: {'courseLessonId': state.courseLessonId}) - .then((value) { - if (value != null) { - Map dataMap = value as Map; - bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, - dataMap['isCompleted'], - currentStep: dataMap['currentStep'], - autoNextSection: dataMap['nextSection'], - )); - } + bloc.audioPlayer.play(AssetSource('reading_time'.assetMp3)); + Future.delayed(const Duration(seconds: 1), () { + //绘本 + pushNamed(AppRouteName.reading, + arguments: {'courseLessonId': state.courseLessonId}) + .then((value) { + if (value != null) { + Map dataMap = value as Map; + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, + dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'], + )); + if (bloc.backgroundPlayer.state == PlayerState.paused) { + bloc.backgroundPlayer.resume(); + } + } + }); }); + return; } if (state.courseType == SectionType.practice.value) { //练习 - AudioPlayer().play(AssetSource('quiz_time'.assetMp3)); - pushNamed(AppRouteName.topicPic, - arguments: {'courseLessonId': state.courseLessonId}) - .then((value) { - if (value != null) { - Map dataMap = value as Map; - bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, dataMap['isCompleted'], - currentStep: dataMap['currentStep'], - autoNextSection: dataMap['nextSection'])); - } + bloc.audioPlayer.play(AssetSource('quiz_time'.assetMp3)); + Future.delayed(const Duration(seconds: 1), () { + pushNamed(AppRouteName.topicPic, + arguments: {'courseLessonId': state.courseLessonId}) + .then((value) { + if (value != null) { + Map dataMap = value as Map; + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'])); + } + if (bloc.backgroundPlayer.state == PlayerState.paused) { + bloc.backgroundPlayer.resume(); + } + }); }); return; } diff --git a/lib/pages/unit/widget/course_unit_item.dart b/lib/pages/unit/widget/course_unit_item.dart index e47a09a..9288fad 100644 --- a/lib/pages/unit/widget/course_unit_item.dart +++ b/lib/pages/unit/widget/course_unit_item.dart @@ -17,14 +17,13 @@ class CourseUnitItem extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h), - child: Stack( - children: [ - _normalItem(), - _lockWidget(), - ], - ) - ); + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h), + child: Stack( + children: [ + _normalItem(), + _lockWidget(), + ], + )); } Widget _normalItem() { @@ -40,17 +39,17 @@ class CourseUnitItem extends StatelessWidget { children: [ Expanded( child: Container( - decoration: BoxDecoration( - border: Border.all( - width: 2, - color: const Color(0xFF140C10), - ), - borderRadius: BorderRadius.circular(6)), - child: OwImageWidget( - name: unitLesson.coverUrl ?? '', - fit: BoxFit.fitHeight, + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: const Color(0xFF140C10), ), - )), + borderRadius: BorderRadius.circular(6)), + child: OwImageWidget( + name: unitLesson.coverUrl ?? '', + fit: BoxFit.fitHeight, + ), + )), 20.verticalSpace, SizedBox( height: 40.h, @@ -58,8 +57,7 @@ class CourseUnitItem extends StatelessWidget { unitLesson.name ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, - style: - TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)), + style: TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)), ), ) ], @@ -75,12 +73,8 @@ class CourseUnitItem extends StatelessWidget { width: 165.w, decoration: BoxDecoration( image: DecorationImage( - image: AssetImage( - 'gendubeij_mengban'.assetPng - ), - fit: BoxFit.fill - ) - ), + image: AssetImage('gendubeij_mengban'.assetPng), + fit: BoxFit.fill)), alignment: Alignment.center, child: Image.asset( 'iv_lock'.assetPng,