Commit 5e2bbbd3eb6c517214a7f3def9c88dba5b32ddf8

Authored by biao
1 parent 7d72793d

添加音频

assets/images/reade_answer.gif 100755 → 100644

2.47 KB | W: | H:

4.78 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
assets/images/voice.png

13 KB | W: | H:

8.91 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
assets/sounds/count_with_me_instrumental.mp3 0 → 100644
No preview for this file type
assets/sounds/in_my_tummy_instrumental.mp3 0 → 100644
No preview for this file type
assets/sounds/touch_instrumental.mp3 0 → 100644
No preview for this file type
ios/Runner.xcodeproj/project.pbxproj
... ... @@ -2327,7 +2327,7 @@
2327 2327 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2328 2328 CODE_SIGN_IDENTITY = "Apple Development";
2329 2329 CODE_SIGN_STYLE = Automatic;
2330   - CURRENT_PROJECT_VERSION = 15;
  2330 + CURRENT_PROJECT_VERSION = 16;
2331 2331 DEVELOPMENT_TEAM = T8P9KW8GWH;
2332 2332 ENABLE_BITCODE = NO;
2333 2333 INFOPLIST_FILE = Runner/Info.plist;
... ... @@ -2671,7 +2671,7 @@
2671 2671 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2672 2672 CODE_SIGN_IDENTITY = "Apple Development";
2673 2673 CODE_SIGN_STYLE = Automatic;
2674   - CURRENT_PROJECT_VERSION = 15;
  2674 + CURRENT_PROJECT_VERSION = 16;
2675 2675 DEVELOPMENT_TEAM = T8P9KW8GWH;
2676 2676 ENABLE_BITCODE = NO;
2677 2677 HEADER_SEARCH_PATHS = (
... ... @@ -2876,7 +2876,7 @@
2876 2876 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2877 2877 CODE_SIGN_IDENTITY = "Apple Development";
2878 2878 CODE_SIGN_STYLE = Automatic;
2879   - CURRENT_PROJECT_VERSION = 15;
  2879 + CURRENT_PROJECT_VERSION = 16;
2880 2880 DEVELOPMENT_TEAM = T8P9KW8GWH;
2881 2881 ENABLE_BITCODE = NO;
2882 2882 INFOPLIST_FILE = Runner/Info.plist;
... ...
lib/pages/home/bloc.dart
... ... @@ -16,9 +16,25 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
16 16 }
17 17  
18 18 bool exchangeResult = false;
  19 + late AudioPlayer audioPlayer;
  20 + late AudioPlayer studyPlayer;
  21 + late AudioPlayer gamePlayer;
  22 + @override
  23 + Future<void> close() {
  24 + audioPlayer.release();
  25 + audioPlayer.dispose();
  26 + studyPlayer.release();
  27 + studyPlayer.dispose();
  28 + gamePlayer.release();
  29 + gamePlayer.dispose();
  30 + return super.close();
  31 + }
19 32  
20 33 void _init(InitEvent event, Emitter<HomeState> emit) async {
21   - AudioPlayer().play(AssetSource('welcome_to_wow'.assetMp3));
  34 + audioPlayer = AudioPlayer(playerId: 'audio');
  35 + gamePlayer = AudioPlayer(playerId: 'game');
  36 + studyPlayer = AudioPlayer(playerId: 'study');
  37 + audioPlayer.play(AssetSource('welcome_to_wow'.assetMp3));
22 38 await _checkUpdate(emit);
23 39 }
24 40  
... ...
lib/pages/home/view.dart
... ... @@ -71,7 +71,7 @@ class _HomePageView extends StatelessWidget {
71 71 child: GestureDetector(
72 72 onTap: () {
73 73 _checkPermission(() {
74   - AudioPlayer()
  74 + bloc.studyPlayer
75 75 .play(AssetSource('class_time'.assetMp3));
76 76 pushNamed(AppRouteName.courseUnit)
77 77 .then((value) => {
... ... @@ -136,7 +136,7 @@ class _HomePageView extends StatelessWidget {
136 136 return GestureDetector(
137 137 onTap: () {
138 138 _checkPermission(() {
139   - AudioPlayer().play(
  139 + bloc.gamePlayer.play(
140 140 AssetSource('game_time'.assetMp3));
141 141 pushNamed(AppRouteName.games);
142 142 }, bloc);
... ...
lib/pages/reading/reading_page.dart
... ... @@ -155,6 +155,7 @@ class _ReadingPage extends StatelessWidget {
155 155 margin: EdgeInsets.symmetric(horizontal: 10.w),
156 156 child: Row(
157 157 children: [
  158 + 5.horizontalSpace,
158 159 GestureDetector(
159 160 onTap: () {
160 161 if (bloc.isRecording) {
... ...
lib/pages/section/bloc/section_bloc.dart
  1 +import 'package:audioplayers/audioplayers.dart';
1 2 import 'package:flutter/cupertino.dart';
2 3 import 'package:flutter/foundation.dart';
3 4 import 'package:flutter/material.dart';
4 5 import 'package:flutter_bloc/flutter_bloc.dart';
5 6 import 'package:flutter_screenutil/flutter_screenutil.dart';
  7 +import 'package:wow_english/common/extension/string_extension.dart';
6 8 import 'package:wow_english/common/request/dao/lesson_dao.dart';
7 9 import 'package:wow_english/common/request/exception.dart';
8 10 import 'package:wow_english/common/request/dao/listen_dao.dart';
... ... @@ -43,6 +45,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
43 45 ///单元列表是否有刷新,有的话返回上一页时通知其刷新接口数据
44 46 bool courseUnitEntityChanged = false;
45 47  
  48 + late AudioPlayer audioPlayer; // 点击播放器
  49 + late AudioPlayer backgroundPlayer; // 背景播放器
46 50 ///courseUnitId与课程环节列表的映射
47 51 final Map<int, List<CourseSectionEntity>?> _courseSectionDatasMap = {};
48 52  
... ... @@ -61,16 +65,48 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
61 65 on<RequestEnterClassEvent>(_requestEnterClass);
62 66 on<RequestVideoLessonEvent>(_requestVideoLesson);
63 67 on<CurrentUnitIndexChangeEvent>(_pageControllerChange);
  68 + on<InitEvent>(_init);
  69 + on<InitBlocEvent>((event, emit) {
  70 + // audioPlayer = AudioPlayer(playerId: 'section');
  71 + // backgroundPlayer = AudioPlayer(playerId: 'back');
  72 + // backgroundPlayer.onPlayerStateChanged.listen((event) async {
  73 + // if (event == PlayerState.completed) {
  74 + // debugPrint('播放结束');
  75 + // backgroundPlayer
  76 + // .play(AssetSource('count_with_me_instrumental'.assetMp3));
  77 + // }
  78 + // });
  79 + // audioPlayer.onPlayerStateChanged.listen((event) async {
  80 + // if (event == PlayerState.completed) {
  81 + // debugPrint('播放结束');
  82 + // }
  83 + // });
  84 + });
  85 + }
  86 + @override
  87 + Future<void> close() {
  88 + audioPlayer.release();
  89 + audioPlayer.dispose();
  90 + backgroundPlayer.release();
  91 + backgroundPlayer.dispose();
  92 + return super.close();
  93 + }
  94 +
  95 + void _init(InitEvent event, Emitter<SectionState> emit) async {
  96 + audioPlayer = AudioPlayer(playerId: 'section');
  97 + // backgroundPlayer = AudioPlayer(playerId: 'back');
  98 + // backgroundPlayer.play(AssetSource('count_with_me_instrumental'.assetMp3));
64 99 }
65 100  
66 101 void _requestSectionsData(
67 102 RequestDataEvent event, Emitter<SectionState> emitter) async {
68 103 try {
69 104 await loading(() async {
70   - List<CourseSectionEntity>? courseSectionEntities = await LessonDao.courseSection(courseUnitId: event.courseUnitId);
  105 + List<CourseSectionEntity>? courseSectionEntities =
  106 + await LessonDao.courseSection(courseUnitId: event.courseUnitId);
71 107 if (courseSectionEntities != null) {
72 108 _courseSectionDatasMap[event.courseUnitId] =
73   - await LessonDao.courseSection(courseUnitId: event.courseUnitId);
  109 + await LessonDao.courseSection(courseUnitId: event.courseUnitId);
74 110 emitter(LessonDataLoadState());
75 111 }
76 112 });
... ... @@ -224,7 +260,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
224 260 ///查找当前unit的下一个section
225 261 CourseSectionEntity? nextCourseSectionEntity =
226 262 findCourseSectionBySort(curSectionSort + 1);
227   - return checkCourseSectionLocked(courseLessonId, nextCourseSectionEntity, emitter);
  263 + return checkCourseSectionLocked(
  264 + courseLessonId, nextCourseSectionEntity, emitter);
228 265 } catch (e) {
229 266 if (e is ApiException) {
230 267 showToast(e.message.toString());
... ... @@ -234,7 +271,9 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
234 271 }
235 272  
236 273 ///检查section是否锁定
237   - Future<CourseSectionEntity?> checkCourseSectionLocked(int courseLessonId, CourseSectionEntity? courseSectionEntity,
  274 + Future<CourseSectionEntity?> checkCourseSectionLocked(
  275 + int courseLessonId,
  276 + CourseSectionEntity? courseSectionEntity,
238 277 Emitter<SectionState> emitter) async {
239 278 if (courseSectionEntity != null) {
240 279 if (courseSectionEntity.lock == false) {
... ... @@ -243,15 +282,15 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
243 282 } else {
244 283 ///如果section锁了,请求当前unit下的section数据,查询解锁状态
245 284 int courseUnitId = courseSectionEntity.courseUnitId;
246   - CourseSectionEntity? result = await loading(() async {
  285 + CourseSectionEntity? result = await loading(() async {
247 286 List<CourseSectionEntity>? tempSectionEntities =
248   - await LessonDao.courseSection(courseUnitId: courseUnitId);
  287 + await LessonDao.courseSection(courseUnitId: courseUnitId);
249 288 if (tempSectionEntities != null) {
250 289 _courseSectionDatasMap[courseUnitId] = tempSectionEntities;
251 290 emitter(LessonDataLoadState());
252 291 }
253 292 courseSectionEntity = tempSectionEntities?.firstWhereOrNull(
254   - (element) => element.id == courseSectionEntity?.id);
  293 + (element) => element.id == courseSectionEntity?.id);
255 294 if (courseSectionEntity?.lock == false) {
256 295 ///刷新后的数据如果解锁了,直接返回
257 296 return courseSectionEntity;
... ... @@ -270,18 +309,20 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
270 309 if (curCourseUnitDetail != null) {
271 310 ///再根据当前unit的sortOrder找出下一个unit
272 311 CourseUnitDetail? nextCourseUnitDetail =
273   - _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
274   - element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
  312 + _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
  313 + element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
275 314  
276 315 if (nextCourseUnitDetail != null) {
277 316 if (nextCourseUnitDetail.lock == true) {
278 317 ///如果下一个unit是锁定状态,请求数据刷新查询解锁状态
279 318 CourseSectionEntity? result = await loading(() async {
280   - CourseUnitEntity? newCourseUnitEntity = await LessonDao.courseUnit(
281   - _courseUnitEntity.nowCourseModuleId);
  319 + CourseUnitEntity? newCourseUnitEntity =
  320 + await LessonDao.courseUnit(
  321 + _courseUnitEntity.nowCourseModuleId);
282 322  
283 323 ///拿到重新获取到的unit后,再次判断是否解锁
284   - nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList?.firstWhereOrNull(
  324 + nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList
  325 + ?.firstWhereOrNull(
285 326 (element) => element.id == nextCourseUnitDetail?.id);
286 327 if (nextCourseUnitDetail?.lock == false) {
287 328 ///解锁状态从锁定到解锁,覆盖原unit数据并刷新ui
... ... @@ -289,7 +330,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
289 330 courseUnitEntityChanged = true;
290 331 emitter(LessonDataLoadState());
291 332  
292   - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail!.id!, emitter);
  333 + return checkCourseSectionLockedOfNextUnit(
  334 + courseLessonId, nextCourseUnitDetail!.id!, emitter);
293 335 } else {
294 336 showToast('下个单元课程还没解锁哦');
295 337  
... ... @@ -299,10 +341,12 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
299 341 });
300 342 return result;
301 343 } else {
302   - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail.id!, emitter);
  344 + return checkCourseSectionLockedOfNextUnit(
  345 + courseLessonId, nextCourseUnitDetail.id!, emitter);
303 346 }
304 347 } else {
305 348 showToast("恭喜你,本阶段学到顶啦");
  349 +
306 350 ///最后一个unit了
307 351 return null;
308 352 }
... ... @@ -314,13 +358,16 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
314 358 }
315 359  
316 360 ///检查下一个unit的(第一个)section
317   - Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(int courseLessonId, int nextCourseUnitDetailId,
  361 + Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(
  362 + int courseLessonId,
  363 + int nextCourseUnitDetailId,
318 364 Emitter<SectionState> emitter) async {
319   - CourseSectionEntity? firstSectionNextUnit = await getFirstSectionByUnitId(
320   - nextCourseUnitDetailId, emitter);
  365 + CourseSectionEntity? firstSectionNextUnit =
  366 + await getFirstSectionByUnitId(nextCourseUnitDetailId, emitter);
321 367 if (firstSectionNextUnit != null) {
322 368 ///下个unit的第一个section如果不为空,再次检查是否锁定
323   - CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(courseLessonId, firstSectionNextUnit, emitter);
  369 + CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(
  370 + courseLessonId, firstSectionNextUnit, emitter);
324 371 if (courseSectionEntity != null) {
325 372 ///只有是下一unit的第一个section并且解锁了,才跳转
326 373 _pageController.nextPage(
... ...
lib/pages/section/bloc/section_event.dart
... ... @@ -9,6 +9,8 @@ class RequestDataEvent extends SectionEvent {
9 9 RequestDataEvent(this.courseUnitId);
10 10 }
11 11  
  12 +class InitEvent extends SectionEvent {}
  13 +
12 14 ///获取视频课程内容
13 15 class RequestVideoLessonEvent extends SectionEvent {
14 16 final String courseLessonId;
... ... @@ -53,3 +55,5 @@ class CurrentUnitIndexChangeEvent extends SectionEvent {
53 55  
54 56 CurrentUnitIndexChangeEvent(this.unitIndex);
55 57 }
  58 +
  59 +class InitBlocEvent extends SectionEvent {}
... ...
lib/pages/section/bloc/section_state.dart
... ... @@ -22,3 +22,5 @@ class RequestEnterClassState extends SectionState {
22 22 }
23 23  
24 24 class CurrentPageIndexState extends SectionState {}
  25 +
  26 +class VoicePlayChangeState extends SectionState {}
... ...
lib/pages/section/section_page.dart
... ... @@ -39,7 +39,8 @@ class SectionPage extends StatelessWidget {
39 39 initialPage,
40 40 PageController(initialPage: initialPage),
41 41 ScrollController(),
42   - ScrollController()),
  42 + ScrollController())
  43 + ..add(InitEvent()),
43 44 //为了触发指示器进入后计算位置
44 45 // ..add(CurrentUnitIndexChangeEvent(initialPage)),
45 46 child: _SectionPageView(context),
... ... @@ -88,57 +89,75 @@ class _SectionPageView extends StatelessWidget {
88 89 currentTime: dataMap['currentTime'],
89 90 autoNextSection: dataMap['nextSection']));
90 91 }
  92 + if (bloc.backgroundPlayer.state == PlayerState.paused) {
  93 + bloc.backgroundPlayer.resume();
  94 + }
91 95 });
92 96 return;
93 97 }
94 98  
95 99 if (state is RequestEnterClassState) {
  100 + bloc.backgroundPlayer.pause();
96 101 if (state.courseType != SectionType.practice.value &&
97 102 state.courseType != SectionType.pictureBook.value) {
98 103 ///视频类型
99 104 ///获取视频课程内容
100 105 if (state.courseType == 1) {
101   - AudioPlayer().play(AssetSource('music_time'.assetMp3));
  106 + bloc.audioPlayer.play(AssetSource('music_time'.assetMp3));
102 107 } else {
103   - AudioPlayer().play(AssetSource('video_time'.assetMp3));
  108 + bloc.audioPlayer.play(AssetSource('video_time'.assetMp3));
104 109 }
105   - bloc.add(RequestVideoLessonEvent(
106   - state.courseLessonId, state.courseType));
  110 + Future.delayed(const Duration(seconds: 1), () {
  111 + bloc.add(RequestVideoLessonEvent(
  112 + state.courseLessonId, state.courseType));
  113 + });
  114 +
107 115 return;
108 116 }
109 117  
110 118 if (state.courseType == SectionType.pictureBook.value) {
111   - AudioPlayer().play(AssetSource('reading_time'.assetMp3));
112   - //绘本
113   - pushNamed(AppRouteName.reading,
114   - arguments: {'courseLessonId': state.courseLessonId})
115   - .then((value) {
116   - if (value != null) {
117   - Map<String, dynamic> dataMap = value as Map<String, dynamic>;
118   - bloc.add(RequestEndClassEvent(
119   - dataMap['courseLessonId']!,
120   - dataMap['isCompleted'],
121   - currentStep: dataMap['currentStep'],
122   - autoNextSection: dataMap['nextSection'],
123   - ));
124   - }
  119 + bloc.audioPlayer.play(AssetSource('reading_time'.assetMp3));
  120 + Future.delayed(const Duration(seconds: 1), () {
  121 + //绘本
  122 + pushNamed(AppRouteName.reading,
  123 + arguments: {'courseLessonId': state.courseLessonId})
  124 + .then((value) {
  125 + if (value != null) {
  126 + Map<String, dynamic> dataMap = value as Map<String, dynamic>;
  127 + bloc.add(RequestEndClassEvent(
  128 + dataMap['courseLessonId']!,
  129 + dataMap['isCompleted'],
  130 + currentStep: dataMap['currentStep'],
  131 + autoNextSection: dataMap['nextSection'],
  132 + ));
  133 + if (bloc.backgroundPlayer.state == PlayerState.paused) {
  134 + bloc.backgroundPlayer.resume();
  135 + }
  136 + }
  137 + });
125 138 });
  139 +
126 140 return;
127 141 }
128 142  
129 143 if (state.courseType == SectionType.practice.value) {
130 144 //练习
131   - AudioPlayer().play(AssetSource('quiz_time'.assetMp3));
132   - pushNamed(AppRouteName.topicPic,
133   - arguments: {'courseLessonId': state.courseLessonId})
134   - .then((value) {
135   - if (value != null) {
136   - Map<String, dynamic> dataMap = value as Map<String, dynamic>;
137   - bloc.add(RequestEndClassEvent(
138   - dataMap['courseLessonId']!, dataMap['isCompleted'],
139   - currentStep: dataMap['currentStep'],
140   - autoNextSection: dataMap['nextSection']));
141   - }
  145 + bloc.audioPlayer.play(AssetSource('quiz_time'.assetMp3));
  146 + Future.delayed(const Duration(seconds: 1), () {
  147 + pushNamed(AppRouteName.topicPic,
  148 + arguments: {'courseLessonId': state.courseLessonId})
  149 + .then((value) {
  150 + if (value != null) {
  151 + Map<String, dynamic> dataMap = value as Map<String, dynamic>;
  152 + bloc.add(RequestEndClassEvent(
  153 + dataMap['courseLessonId']!, dataMap['isCompleted'],
  154 + currentStep: dataMap['currentStep'],
  155 + autoNextSection: dataMap['nextSection']));
  156 + }
  157 + if (bloc.backgroundPlayer.state == PlayerState.paused) {
  158 + bloc.backgroundPlayer.resume();
  159 + }
  160 + });
142 161 });
143 162 return;
144 163 }
... ...
lib/pages/unit/widget/course_unit_item.dart
... ... @@ -17,14 +17,13 @@ class CourseUnitItem extends StatelessWidget {
17 17 @override
18 18 Widget build(BuildContext context) {
19 19 return Padding(
20   - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h),
21   - child: Stack(
22   - children: [
23   - _normalItem(),
24   - _lockWidget(),
25   - ],
26   - )
27   - );
  20 + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h),
  21 + child: Stack(
  22 + children: [
  23 + _normalItem(),
  24 + _lockWidget(),
  25 + ],
  26 + ));
28 27 }
29 28  
30 29 Widget _normalItem() {
... ... @@ -40,17 +39,17 @@ class CourseUnitItem extends StatelessWidget {
40 39 children: [
41 40 Expanded(
42 41 child: Container(
43   - decoration: BoxDecoration(
44   - border: Border.all(
45   - width: 2,
46   - color: const Color(0xFF140C10),
47   - ),
48   - borderRadius: BorderRadius.circular(6)),
49   - child: OwImageWidget(
50   - name: unitLesson.coverUrl ?? '',
51   - fit: BoxFit.fitHeight,
  42 + decoration: BoxDecoration(
  43 + border: Border.all(
  44 + width: 2,
  45 + color: const Color(0xFF140C10),
52 46 ),
53   - )),
  47 + borderRadius: BorderRadius.circular(6)),
  48 + child: OwImageWidget(
  49 + name: unitLesson.coverUrl ?? '',
  50 + fit: BoxFit.fitHeight,
  51 + ),
  52 + )),
54 53 20.verticalSpace,
55 54 SizedBox(
56 55 height: 40.h,
... ... @@ -58,8 +57,7 @@ class CourseUnitItem extends StatelessWidget {
58 57 unitLesson.name ?? '',
59 58 maxLines: 2,
60 59 overflow: TextOverflow.ellipsis,
61   - style:
62   - TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)),
  60 + style: TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)),
63 61 ),
64 62 )
65 63 ],
... ... @@ -75,12 +73,8 @@ class CourseUnitItem extends StatelessWidget {
75 73 width: 165.w,
76 74 decoration: BoxDecoration(
77 75 image: DecorationImage(
78   - image: AssetImage(
79   - 'gendubeij_mengban'.assetPng
80   - ),
81   - fit: BoxFit.fill
82   - )
83   - ),
  76 + image: AssetImage('gendubeij_mengban'.assetPng),
  77 + fit: BoxFit.fill)),
84 78 alignment: Alignment.center,
85 79 child: Image.asset(
86 80 'iv_lock'.assetPng,
... ...