Commit 22f362320722bc706da82d9fd40629dde0345b76
1 parent
aa0d2360
feat:过渡页-练习环节
Showing
9 changed files
with
154 additions
and
60 deletions
lib/pages/practice/bloc/topic_picture_bloc.dart
... | ... | @@ -9,10 +9,14 @@ import 'package:wow_english/common/extension/string_extension.dart'; |
9 | 9 | import 'package:wow_english/common/request/dao/listen_dao.dart'; |
10 | 10 | import 'package:wow_english/common/request/exception.dart'; |
11 | 11 | import 'package:wow_english/models/course_process_entity.dart'; |
12 | +import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; | |
13 | +import 'package:wow_english/pages/section/subsection/base_section/event.dart'; | |
14 | +import 'package:wow_english/pages/section/subsection/base_section/state.dart'; | |
12 | 15 | import 'package:wow_english/utils/loading.dart'; |
13 | 16 | import 'package:wow_english/utils/toast_util.dart'; |
14 | 17 | |
15 | 18 | import '../../../common/permission/permissionRequestPage.dart'; |
19 | +import '../../../route/route.dart'; | |
16 | 20 | |
17 | 21 | part 'topic_picture_event.dart'; |
18 | 22 | part 'topic_picture_state.dart'; |
... | ... | @@ -28,7 +32,7 @@ enum VoicePlayState { |
28 | 32 | stop |
29 | 33 | } |
30 | 34 | |
31 | -class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { | |
35 | +class TopicPictureBloc extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> { | |
32 | 36 | |
33 | 37 | final PageController pageController; |
34 | 38 | |
... | ... | @@ -94,11 +98,15 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
94 | 98 | _forbiddenWhenCorrect = false; |
95 | 99 | debugPrint('播放完成后解除禁止'); |
96 | 100 | if (event == PlayerState.completed) { |
97 | - // 答对后且播放完自动翻页 | |
98 | - pageController.nextPage( | |
99 | - duration: const Duration(milliseconds: 500), | |
100 | - curve: Curves.ease, | |
101 | - ); | |
101 | + if (isLastPage()) { | |
102 | + showStepPage(); | |
103 | + } else { | |
104 | + // 答对后且播放完自动翻页 | |
105 | + pageController.nextPage( | |
106 | + duration: const Duration(milliseconds: 500), | |
107 | + curve: Curves.ease, | |
108 | + ); | |
109 | + } | |
102 | 110 | } |
103 | 111 | } |
104 | 112 | } |
... | ... | @@ -263,6 +271,9 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
263 | 271 | showToast('测评成功,分数是$overall',duration: const Duration(seconds: 5)); |
264 | 272 | _isVoicing = false; |
265 | 273 | emitter(XSVoiceTestState()); |
274 | + if (isLastPage()) { | |
275 | + showStepPage(); | |
276 | + } | |
266 | 277 | } |
267 | 278 | |
268 | 279 | // 暂时没用上 |
... | ... | @@ -288,6 +299,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
288 | 299 | } |
289 | 300 | } |
290 | 301 | |
302 | + ///播放选择结果音效 | |
291 | 303 | void _playResultSound(bool isCorrect) async { |
292 | 304 | // await audioPlayer.stop(); |
293 | 305 | if (audioPlayer.state == PlayerState.playing && _isResultSoundPlaying == false) { |
... | ... | @@ -302,4 +314,26 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
302 | 314 | await audioPlayer.play(AssetSource('incorrect_voice'.assetMp3)); |
303 | 315 | } |
304 | 316 | } |
317 | + | |
318 | + ///是否是最后一页 | |
319 | + bool isLastPage() { | |
320 | + return currentPage == _entity?.topics?.length; | |
321 | + } | |
322 | + | |
323 | + ///展示过渡页 | |
324 | + void showStepPage() { | |
325 | + ///如果最后一页是语音问答题,评测完后自动翻页 | |
326 | + sectionComplete(() { | |
327 | + popPage( | |
328 | + data:{ | |
329 | + 'currentStep':currentPage.toString(), | |
330 | + 'courseLessonId':courseLessonId, | |
331 | + 'isLastPage': true, | |
332 | + 'nextSection': true | |
333 | + }); | |
334 | + }, againSectionTap: () { | |
335 | + debugPrint("WQF 重做"); | |
336 | + pageController.jumpToPage(0); | |
337 | + }); | |
338 | + } | |
305 | 339 | } | ... | ... |
lib/pages/practice/bloc/topic_picture_event.dart
lib/pages/practice/bloc/topic_picture_state.dart
lib/pages/practice/topic_picture_page.dart
... | ... | @@ -6,6 +6,7 @@ import 'package:wow_english/common/core/user_util.dart'; |
6 | 6 | import 'package:wow_english/common/extension/string_extension.dart'; |
7 | 7 | import 'package:wow_english/common/widgets/ow_image_widget.dart'; |
8 | 8 | import 'package:wow_english/models/course_process_entity.dart'; |
9 | +import 'package:wow_english/pages/practice/topic_type.dart'; | |
9 | 10 | import 'package:wow_english/route/route.dart'; |
10 | 11 | import 'package:wow_english/utils/toast_util.dart'; |
11 | 12 | |
... | ... | @@ -71,7 +72,8 @@ class _TopicPicturePage extends StatelessWidget { |
71 | 72 | popPage( |
72 | 73 | data:{ |
73 | 74 | 'currentStep':bloc.currentPage.toString(), |
74 | - 'courseLessonId':bloc.courseLessonId | |
75 | + 'courseLessonId':bloc.courseLessonId, | |
76 | + 'isLastPage': bloc.isLastPage(), | |
75 | 77 | }); |
76 | 78 | // Navigator.pop(context); |
77 | 79 | }, |
... | ... | @@ -86,13 +88,13 @@ class _TopicPicturePage extends StatelessWidget { |
86 | 88 | }, |
87 | 89 | itemBuilder: (BuildContext context,int index){ |
88 | 90 | CourseProcessTopics? topics = bloc.entity?.topics![index]; |
89 | - if (topics?.type == 1) {//听音选图 | |
91 | + if (topics?.type == TopicType.audioImageSelect.value) {//听音选图 | |
90 | 92 | return _pageViewVoicePictureItemWidget(topics); |
91 | - } else if (topics?.type == 2) {//听音选字 | |
93 | + } else if (topics?.type == TopicType.audioCharSelect.value) {//听音选字 | |
92 | 94 | return _pageViewVoiceWordItemWidget(topics); |
93 | - } else if (topics?.type == 3) {//看题选字 | |
95 | + } else if (topics?.type == TopicType.questionCharSelect.value) {//看题选字 | |
94 | 96 | return _pageViewWordItemWidget(topics); |
95 | - } else if (topics?.type == 4) {//看题选图 | |
97 | + } else if (topics?.type == TopicType.questionImageSelect.value) {//看题选图 | |
96 | 98 | return _pageViewItemWidget(topics); |
97 | 99 | } else {//语音问答 |
98 | 100 | return _voiceAnswerItem(topics); | ... | ... |
lib/pages/practice/topic_type.dart
0 → 100644
1 | +///练习(题型)类型 | |
2 | +enum TopicType { | |
3 | + ///听音选图 | |
4 | + audioImageSelect, | |
5 | + | |
6 | + ///听音选字 | |
7 | + audioCharSelect, | |
8 | + | |
9 | + ///看题选字 | |
10 | + questionCharSelect, | |
11 | + | |
12 | + ///看题选图 | |
13 | + questionImageSelect, | |
14 | + | |
15 | + ///语音问答 | |
16 | + voiceQuestion | |
17 | +} | |
18 | + | |
19 | +extension TopicTypeExtension on TopicType { | |
20 | + int get value { | |
21 | + switch (this) { | |
22 | + case TopicType.audioImageSelect: | |
23 | + return 1; | |
24 | + case TopicType.audioCharSelect: | |
25 | + return 2; | |
26 | + case TopicType.questionCharSelect: | |
27 | + return 3; | |
28 | + case TopicType.questionImageSelect: | |
29 | + return 4; | |
30 | + case TopicType.voiceQuestion: | |
31 | + return 5; | |
32 | + default: | |
33 | + throw ArgumentError('Unknown topic type'); | |
34 | + } | |
35 | + } | |
36 | +} | ... | ... |
lib/pages/section/bloc/section_bloc.dart
... | ... | @@ -46,19 +46,22 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
46 | 46 | |
47 | 47 | CourseProcessEntity? get processEntity => _processEntity; |
48 | 48 | |
49 | - SectionBloc(this._courseUnitEntity, this._courseUnitDetail, this._pageController, this._listController) : super(LessonInitial()) { | |
49 | + SectionBloc(this._courseUnitEntity, this._courseUnitDetail, | |
50 | + this._pageController, this._listController) | |
51 | + : super(LessonInitial()) { | |
50 | 52 | on<RequestDataEvent>(_requestData); |
51 | - on<RequestExitClassEvent>(_requestExitClass); | |
52 | 53 | on<RequestEndClassEvent>(_requestEndClass); |
53 | 54 | on<RequestEnterClassEvent>(_requestEnterClass); |
54 | 55 | on<RequestVideoLessonEvent>(_requestVideoLesson); |
55 | 56 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); |
56 | 57 | } |
57 | 58 | |
58 | - void _requestData(RequestDataEvent event, Emitter<SectionState> emitter) async { | |
59 | + void _requestData( | |
60 | + RequestDataEvent event, Emitter<SectionState> emitter) async { | |
59 | 61 | try { |
60 | 62 | await loading(() async { |
61 | - _courseSectionDatas = await LessonDao.courseSection(courseUnitId: _courseUnitDetail.id!); | |
63 | + _courseSectionDatas = | |
64 | + await LessonDao.courseSection(courseUnitId: _courseUnitDetail.id!); | |
62 | 65 | emitter(LessonDataLoadState()); |
63 | 66 | }); |
64 | 67 | } catch (e) { |
... | ... | @@ -68,68 +71,84 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
68 | 71 | } |
69 | 72 | } |
70 | 73 | |
71 | - void _requestVideoLesson(RequestVideoLessonEvent event, Emitter<SectionState> emitter) async { | |
74 | + void _requestVideoLesson( | |
75 | + RequestVideoLessonEvent event, Emitter<SectionState> emitter) async { | |
72 | 76 | try { |
73 | 77 | await loading(() async { |
74 | 78 | _processEntity = await ListenDao.process(event.courseLessonId); |
75 | - emitter(RequestVideoLessonState(event.courseLessonId,event.courseType)); | |
79 | + emitter( | |
80 | + RequestVideoLessonState(event.courseLessonId, event.courseType)); | |
76 | 81 | }); |
77 | 82 | } catch (e) { |
78 | 83 | if (e is ApiException) { |
79 | - showToast(e.message??'请求失败,请检查网络连接'); | |
84 | + showToast(e.message ?? '请求失败,请检查网络连接'); | |
80 | 85 | } |
81 | 86 | } |
82 | 87 | } |
83 | 88 | |
84 | - | |
85 | - void _requestEnterClass(RequestEnterClassEvent event,Emitter<SectionState> emitter) async { | |
89 | + void _requestEnterClass( | |
90 | + RequestEnterClassEvent event, Emitter<SectionState> emitter) async { | |
86 | 91 | try { |
87 | 92 | await loading(() async { |
88 | - await ListenDao.enterClass(event.courseLessonId); | |
89 | - emitter(RequestEnterClassState(event.courseLessonId,event.courseType)); | |
93 | + await ListenDao.enterClass(event.courseLessonId); | |
94 | + emitter(RequestEnterClassState(event.courseLessonId, event.courseType)); | |
90 | 95 | }); |
91 | 96 | } catch (e) { |
92 | 97 | if (e is ApiException) { |
93 | - showToast(e.message??'请求失败,请检查网络连接'); | |
98 | + showToast(e.message ?? '请求失败,请检查网络连接'); | |
94 | 99 | } |
95 | 100 | } |
96 | 101 | } |
97 | 102 | |
98 | - void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { | |
99 | - await ListenDao.exitClass(event.courseLessonId,event.currentStep); | |
100 | - } | |
101 | - | |
102 | - void _requestEndClass(RequestEndClassEvent event,Emitter<SectionState> emitter) async { | |
103 | - final obj = await ListenDao.endClass(event.courseLessonId,event.currentStep); | |
103 | + void _requestEndClass( | |
104 | + RequestEndClassEvent event, Emitter<SectionState> emitter) async { | |
105 | + if (event.isLastPage) { | |
106 | + await await ListenDao.endClass(event.courseLessonId, event.currentStep, | |
107 | + currentTime: event.currentTime); | |
108 | + } else { | |
109 | + await await ListenDao.exitClass(event.courseLessonId, event.currentStep, | |
110 | + currentTime: event.currentTime); | |
111 | + } | |
112 | + debugPrint("WQF _requestEndClass autoNextSection=${event.autoNextSection}"); | |
104 | 113 | if (event.autoNextSection) { |
105 | - final nextCourseSection = getNextCourseSectionBySort(int.parse(event.courseLessonId)); | |
114 | + final nextCourseSection = | |
115 | + getNextCourseSectionBySort(int.parse(event.courseLessonId)); | |
116 | + debugPrint("WQF nextCourseSection = $nextCourseSection"); | |
106 | 117 | ///进入课堂 |
107 | - add(RequestEnterClassEvent(nextCourseSection!.id.toString() ?? '', nextCourseSection.courseType)); | |
118 | + add(RequestEnterClassEvent(nextCourseSection!.id.toString(), | |
119 | + nextCourseSection.courseType)); | |
108 | 120 | } |
109 | 121 | } |
110 | 122 | |
111 | - void _pageControllerChange(CurrentUnitIndexChangeEvent event, | |
112 | - Emitter<SectionState> emitter) async { | |
123 | + void _pageControllerChange( | |
124 | + CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async { | |
113 | 125 | _currentPage = event.unitIndex; |
114 | 126 | emitter(CurrentPageIndexState()); |
115 | 127 | } |
116 | 128 | |
117 | 129 | // 未锁定的页数 |
118 | 130 | int unlockPageCount() { |
119 | - return _courseUnitEntity.courseUnitVOList?.indexWhereOrNull((element) => element.lock == true) ?? 1; | |
131 | + return _courseUnitEntity.courseUnitVOList | |
132 | + ?.indexWhereOrNull((element) => element.lock == true) ?? | |
133 | + 1; | |
120 | 134 | } |
121 | 135 | |
122 | 136 | CourseSectionEntity? getNextCourseSectionBySort(int courseLessonId) { |
123 | - final curCourseSectionEntity = _courseSectionDatas?.firstWhere((element) => element.id == courseLessonId); | |
137 | + final curCourseSectionEntity = _courseSectionDatas | |
138 | + ?.firstWhere((element) => element.id == courseLessonId); | |
124 | 139 | final curSort = curCourseSectionEntity?.sortOrder ?? 0; |
125 | - CourseSectionEntity? nextCourseSectionEntity = _courseSectionDatas?.firstWhere((element) => element.sortOrder == curSort + 1); | |
140 | + CourseSectionEntity? nextCourseSectionEntity = _courseSectionDatas | |
141 | + ?.firstWhere((element) => element.sortOrder == curSort + 1); | |
126 | 142 | if (nextCourseSectionEntity != null) { |
127 | 143 | return nextCourseSectionEntity; |
128 | 144 | } else { |
129 | 145 | ///跨unit选lesson |
130 | - final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList?.firstWhere((element) => element.id == curCourseSectionEntity?.courseUnitId); | |
146 | + final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList | |
147 | + ?.firstWhere( | |
148 | + (element) => element.id == curCourseSectionEntity?.courseUnitId); | |
131 | 149 | if (curCourseUnitDetail != null) { |
132 | - final nextCourseUnitDetail = _courseUnitEntity.courseUnitVOList?.firstWhere((element) => element.sortOrder == 0); | |
150 | + final nextCourseUnitDetail = _courseUnitEntity.courseUnitVOList | |
151 | + ?.firstWhere((element) => element.sortOrder == 0); | |
133 | 152 | if (nextCourseUnitDetail != null) { |
134 | 153 | ///pageView翻页了,可能需要预加载 todo |
135 | 154 | return null; | ... | ... |
lib/pages/section/bloc/section_event.dart
... | ... | @@ -9,6 +9,7 @@ class RequestDataEvent extends SectionEvent {} |
9 | 9 | class RequestVideoLessonEvent extends SectionEvent { |
10 | 10 | final String courseLessonId; |
11 | 11 | final int courseType; |
12 | + | |
12 | 13 | RequestVideoLessonEvent(this.courseLessonId, this.courseType); |
13 | 14 | } |
14 | 15 | |
... | ... | @@ -16,29 +17,29 @@ class RequestVideoLessonEvent extends SectionEvent { |
16 | 17 | class RequestEnterClassEvent extends SectionEvent { |
17 | 18 | final String courseLessonId; |
18 | 19 | final int courseType; |
19 | - RequestEnterClassEvent(this.courseLessonId,this.courseType); | |
20 | -} | |
21 | 20 | |
22 | -///退出课堂 | |
23 | -class RequestExitClassEvent extends SectionEvent { | |
24 | - final String courseLessonId; | |
25 | - final String currentStep; | |
26 | - final String currentTime; | |
27 | - RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); | |
21 | + RequestEnterClassEvent(this.courseLessonId, this.courseType); | |
28 | 22 | } |
29 | 23 | |
30 | 24 | ///结束课堂 |
31 | 25 | class RequestEndClassEvent extends SectionEvent { |
32 | 26 | final String courseLessonId; |
33 | 27 | final String currentStep; |
34 | - final String currentTime; | |
28 | + | |
29 | + ///是否是最后一页(决定调结束接口还是退出接口) | |
30 | + final bool isLastPage; | |
31 | + final int? currentTime; | |
32 | + | |
35 | 33 | ///自动进入下一环节 |
36 | 34 | final bool autoNextSection; |
37 | - RequestEndClassEvent(this.courseLessonId,this.currentStep,this.currentTime,{this.autoNextSection = false}); | |
35 | + | |
36 | + RequestEndClassEvent(this.courseLessonId, this.currentStep, this.isLastPage, | |
37 | + {this.currentTime, this.autoNextSection = false}); | |
38 | 38 | } |
39 | 39 | |
40 | 40 | ///页面切换 |
41 | 41 | class CurrentUnitIndexChangeEvent extends SectionEvent { |
42 | 42 | final int unitIndex; |
43 | + | |
43 | 44 | CurrentUnitIndexChangeEvent(this.unitIndex); |
44 | 45 | } | ... | ... |
lib/pages/section/section_page.dart
... | ... | @@ -76,8 +76,8 @@ class _SectionPageView extends StatelessWidget { |
76 | 76 | }).then((value) { |
77 | 77 | if (value != null) { |
78 | 78 | Map<String, dynamic> dataMap = value as Map<String, dynamic>; |
79 | - bloc.add(RequestEndClassEvent(dataMap['courseLessonId']!, '0', | |
80 | - dataMap['currentTime']!, autoNextSection: dataMap['nextSection'] as bool)); | |
79 | + bloc.add(RequestEndClassEvent(dataMap['courseLessonId']!, | |
80 | + dataMap['currentTime']!, true, autoNextSection: dataMap['nextSection'] as bool)); | |
81 | 81 | } |
82 | 82 | }); |
83 | 83 | return; |
... | ... | @@ -99,9 +99,10 @@ class _SectionPageView extends StatelessWidget { |
99 | 99 | arguments: {'courseLessonId': state.courseLessonId}) |
100 | 100 | .then((value) { |
101 | 101 | if (value != null) { |
102 | - Map<String, String> dataMap = value as Map<String, String>; | |
103 | - bloc.add(RequestExitClassEvent( | |
104 | - dataMap['courseLessonId']!, dataMap['currentStep']!, '0')); | |
102 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
103 | + bloc.add(RequestEndClassEvent( | |
104 | + dataMap['courseLessonId']!, dataMap['currentStep']!, | |
105 | + dataMap['isLastPage']! as bool, autoNextSection: dataMap['nextSection'] as bool)); | |
105 | 106 | } |
106 | 107 | }); |
107 | 108 | return; |
... | ... | @@ -113,9 +114,10 @@ class _SectionPageView extends StatelessWidget { |
113 | 114 | arguments: {'courseLessonId': state.courseLessonId}) |
114 | 115 | .then((value) { |
115 | 116 | if (value != null) { |
116 | - Map<String, String> dataMap = value as Map<String, String>; | |
117 | - bloc.add(RequestExitClassEvent( | |
118 | - dataMap['courseLessonId']!, dataMap['currentStep']!, '0')); | |
117 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
118 | + bloc.add(RequestEndClassEvent( | |
119 | + dataMap['courseLessonId']!, dataMap['currentStep']!, | |
120 | + dataMap['isLastPage']! as bool, autoNextSection: dataMap['nextSection'] as bool)); | |
119 | 121 | } |
120 | 122 | }); |
121 | 123 | return; | ... | ... |
lib/pages/section/subsection/base_section/bloc.dart
... | ... | @@ -14,7 +14,7 @@ abstract class BaseSectionBloc<E extends BaseSectionEvent, |
14 | 14 | |
15 | 15 | ///这里可以定义一些通用的逻辑 |
16 | 16 | void sectionComplete(final VoidCallback? nextSectionTap, |
17 | - {BuildContext? context}) { | |
17 | + {VoidCallback? againSectionTap, BuildContext? context}) { | |
18 | 18 | // 逻辑来标记步骤为已完成 |
19 | 19 | // 比如更新状态 |
20 | 20 | if (isCompleteDialogShow) { |
... | ... | @@ -38,8 +38,8 @@ abstract class BaseSectionBloc<E extends BaseSectionEvent, |
38 | 38 | flex: 1, |
39 | 39 | child: GestureDetector( |
40 | 40 | onTap: () { |
41 | - add(SectionAgainEvent() as E); | |
42 | 41 | popPage(); |
42 | + againSectionTap!(); | |
43 | 43 | }, |
44 | 44 | child: Image.asset('section_finish_again'.assetPng), |
45 | 45 | ), | ... | ... |