Commit 1e22b7d1bfc9bfeef388fe4b909281f759dd02d6

Authored by 吴启风
1 parent 26982eea

feat:儿歌/视频环节接口请求时机优化

lib/common/request/dao/lesson_dao.dart
... ... @@ -13,6 +13,7 @@ class LessonDao {
13 13 }
14 14  
15 15 ///课程单元列表
  16 + ///@param moduleId 模块id
16 17 static Future<CourseUnitEntity?> courseUnit(int? moduleId) async {
17 18 Map<String, dynamic> mapData = {};
18 19 if (moduleId != null) {
... ... @@ -32,7 +33,7 @@ class LessonDao {
32 33 return data;
33 34 }
34 35  
35   - ///课程(单元)列表
  36 + ///课程(环节)列表
36 37 static Future<List<CourseSectionEntity>?> courseSection({required int courseUnitId}) async {
37 38 Map<String, dynamic> mapData = {};
38 39 mapData['courseUnitId'] = courseUnitId;
... ...
lib/pages/practice/bloc/topic_picture_bloc.dart
... ... @@ -19,6 +19,7 @@ import &#39;../../../common/permission/permissionRequester.dart&#39;;
19 19 import '../../../route/route.dart';
20 20  
21 21 part 'topic_picture_event.dart';
  22 +
22 23 part 'topic_picture_state.dart';
23 24  
24 25 enum VoicePlayState {
... ... @@ -47,6 +48,8 @@ class TopicPictureBloc
47 48  
48 49 CourseProcessEntity? _entity;
49 50  
  51 + CourseProcessEntity? get entity => _entity;
  52 +
50 53 ///正在评测
51 54 bool _isVoicing = false;
52 55  
... ... @@ -63,8 +66,6 @@ class TopicPictureBloc
63 66  
64 67 bool get forbiddenWhenCorrect => _forbiddenWhenCorrect;
65 68  
66   - CourseProcessEntity? get entity => _entity;
67   -
68 69 int get currentPage => _currentPage + 1;
69 70  
70 71 int get selectItem => _selectItem;
... ... @@ -257,8 +258,8 @@ class TopicPictureBloc
257 258 XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async {
258 259 await audioPlayer.stop();
259 260 // 调用封装好的权限检查和请求方法
260   - bool result =
261   - await requestPermission(context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
  261 + bool result = await requestPermission(
  262 + context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
262 263 if (result) {
263 264 methodChannel.invokeMethod('startVoice', {
264 265 'word': event.testWord,
... ...
lib/pages/section/bloc/section_bloc.dart
1   -import 'package:audioplayers/audioplayers.dart';
2 1 import 'package:flutter/cupertino.dart';
3 2 import 'package:flutter/foundation.dart';
4 3 import 'package:flutter/material.dart';
5 4 import 'package:flutter_bloc/flutter_bloc.dart';
6 5 import 'package:flutter_screenutil/flutter_screenutil.dart';
7   -import 'package:wow_english/common/extension/string_extension.dart';
8 6 import 'package:wow_english/common/request/dao/lesson_dao.dart';
9 7 import 'package:wow_english/common/request/exception.dart';
10 8 import 'package:wow_english/common/request/dao/listen_dao.dart';
11   -import 'package:wow_english/models/course_process_entity.dart';
12 9 import 'package:wow_english/utils/audio_player_util.dart';
13 10 import 'package:wow_english/utils/loading.dart';
14 11 import 'package:wow_english/utils/toast_util.dart';
... ... @@ -53,10 +50,6 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
53 50 Map<int, List<CourseSectionEntity>?> get courseSectionDatasMap =>
54 51 _courseSectionDatasMap;
55 52  
56   - CourseProcessEntity? _processEntity;
57   -
58   - CourseProcessEntity? get processEntity => _processEntity;
59   -
60 53 ///点击环节后先请求数据再进入,标志位避免频繁点击多次请求(以及多次进入)
61 54 bool _isRequesting = false;
62 55  
... ... @@ -66,7 +59,6 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
66 59 on<RequestDataEvent>(_requestSectionsData);
67 60 on<RequestEndClassEvent>(_requestEndClass);
68 61 on<RequestEnterClassEvent>(_requestEnterClass);
69   - on<RequestVideoLessonEvent>(_requestVideoLesson);
70 62 on<CurrentUnitIndexChangeEvent>(_pageControllerChange);
71 63 on<InitEvent>((event, emit) async {
72 64 await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe);
... ... @@ -92,23 +84,6 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
92 84 }
93 85 }
94 86  
95   - void _requestVideoLesson(
96   - RequestVideoLessonEvent event, Emitter<SectionState> emitter) async {
97   - try {
98   - await loading(() async {
99   - _processEntity = await ListenDao.process(event.courseLessonId);
100   - emitter(
101   - RequestVideoLessonState(event.courseLessonId, event.courseType));
102   - });
103   - } catch (e) {
104   - if (e is ApiException) {
105   - showToast(e.message ?? '请求失败,请检查网络连接');
106   - }
107   - } finally {
108   - _isRequesting = false;
109   - }
110   - }
111   -
112 87 void _requestEnterClass(
113 88 RequestEnterClassEvent event, Emitter<SectionState> emitter) async {
114 89 try {
... ... @@ -124,8 +99,9 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
124 99 } catch (e) {
125 100 if (e is ApiException) {
126 101 showToast(e.message ?? '请求失败,请检查网络连接');
127   - _isRequesting = false;
128 102 }
  103 + } finally {
  104 + _isRequesting = false;
129 105 }
130 106 }
131 107  
... ...
lib/pages/section/bloc/section_event.dart
... ... @@ -11,14 +11,6 @@ class RequestDataEvent extends SectionEvent {
11 11  
12 12 class InitEvent extends SectionEvent {}
13 13  
14   -///获取视频课程内容
15   -class RequestVideoLessonEvent extends SectionEvent {
16   - final String courseLessonId;
17   - final int courseType;
18   -
19   - RequestVideoLessonEvent(this.courseLessonId, this.courseType);
20   -}
21   -
22 14 ///进入课堂
23 15 class RequestEnterClassEvent extends SectionEvent {
24 16 final String courseLessonId;
... ...
lib/pages/section/bloc/section_state.dart
... ... @@ -7,13 +7,6 @@ class LessonInitial extends SectionState {}
7 7  
8 8 class LessonDataLoadState extends SectionState {}
9 9  
10   -class RequestVideoLessonState extends SectionState {
11   - final String courseLessonId;
12   - final int type;
13   -
14   - RequestVideoLessonState(this.courseLessonId, this.type);
15   -}
16   -
17 10 class RequestEnterClassState extends SectionState {
18 11 final String courseLessonId;
19 12 final int courseType;
... ...
lib/pages/section/section_page.dart
... ... @@ -60,58 +60,36 @@ class _SectionPageView extends StatelessWidget {
60 60 final bloc = BlocProvider.of<SectionBloc>(context);
61 61 return BlocListener<SectionBloc, SectionState>(
62 62 listener: (context, state) async {
63   - if (state is RequestVideoLessonState) {
64   - final videoUrl = bloc.processEntity?.videos?.videoUrl ?? '';
65   - var title = '';
66   - if (state.type == SectionType.song.value) {
67   - title = 'song';
68   - }
69   -
70   - if (state.type == SectionType.video.value) {
71   - title = 'video';
72   - }
73   -
74   - if (state.type == SectionType.bouns.value) {
75   - title = 'bonus';
76   - }
77   -
78   - if (videoUrl.isEmpty || !videoUrl.contains('http')) {
79   - return;
80   - }
81   - pushNamed(AppRouteName.lookVideo, arguments: {
82   - 'videoUrl': videoUrl,
83   - 'title': title,
84   - 'courseLessonId': state.courseLessonId,
85   - 'isTopic': true
86   - }).then((value) {
87   - if (value != null) {
88   - Map<String, dynamic> dataMap = value as Map<String, dynamic>;
89   - bloc.add(RequestEndClassEvent(
90   - dataMap['courseLessonId']!, dataMap['isCompleted'],
91   - currentTime: dataMap['currentTime'],
92   - autoNextSection: dataMap['nextSection']));
93   - }
94   - AudioPlayerUtil.getInstance()
95   - .playAudio(AudioPlayerUtilType.countWithMe);
96   - });
97   - return;
98   - }
99   -
100 63 if (state is RequestEnterClassState) {
101   - if (state.courseType != SectionType.practice.value &&
102   - state.courseType != SectionType.pictureBook.value) {
103   - ///视频类型
104   - ///获取视频课程内容
105   - if (state.courseType == 1) {
106   - AudioPlayerUtil.getInstance()
  64 + if (state.courseType == SectionType.song.value &&
  65 + state.courseType == SectionType.video.value) {
  66 + var title =
  67 + state.courseType == SectionType.song.value ? 'song' : 'video';
  68 +
  69 + ///儿歌/视频类型
  70 + if (state.courseType == SectionType.song.value) {
  71 + await AudioPlayerUtil.getInstance()
107 72 .playAudio(AudioPlayerUtilType.musicTime);
108 73 } else {
109   - AudioPlayerUtil.getInstance()
  74 + await AudioPlayerUtil.getInstance()
110 75 .playAudio(AudioPlayerUtilType.videoTime);
111 76 }
112   - bloc.add(RequestVideoLessonEvent(
113   - state.courseLessonId, state.courseType));
114   -
  77 + pushNamed(AppRouteName.lookVideo, arguments: {
  78 + 'videoUrl': "",
  79 + 'title': title,
  80 + 'courseLessonId': state.courseLessonId,
  81 + 'isTopic': true
  82 + }).then((value) {
  83 + if (value != null) {
  84 + Map<String, dynamic> dataMap = value as Map<String, dynamic>;
  85 + bloc.add(RequestEndClassEvent(
  86 + dataMap['courseLessonId']!, dataMap['isCompleted'],
  87 + currentTime: dataMap['currentTime'],
  88 + autoNextSection: dataMap['nextSection']));
  89 + }
  90 + AudioPlayerUtil.getInstance()
  91 + .playAudio(AudioPlayerUtilType.countWithMe);
  92 + });
115 93 return;
116 94 }
117 95  
... ...
lib/pages/video/lookvideo/bloc/look_video_bloc.dart
... ... @@ -5,15 +5,19 @@ import &#39;package:wow_english/pages/section/subsection/base_section/bloc.dart&#39;;
5 5 import 'package:wow_english/pages/section/subsection/base_section/event.dart';
6 6 import 'package:wow_english/pages/section/subsection/base_section/state.dart';
7 7  
  8 +import '../../../../common/request/dao/listen_dao.dart';
  9 +import '../../../../common/request/exception.dart';
  10 +import '../../../../models/course_process_entity.dart';
  11 +import '../../../../utils/loading.dart';
  12 +import '../../../../utils/toast_util.dart';
  13 +
8 14 part 'look_video_event.dart';
9 15 part 'look_video_state.dart';
10 16  
11 17 class LookVideoBloc extends BaseSectionBloc<BaseSectionEvent, BaseSectionState> {
12 18  
13   - VideoPlayerController? _controller;
14   -
15 19 final String? _videoUrl;
16   - String? get videoUrl => _videoUrl;
  20 + String? get videoUrl => _videoUrl ?? _entity?.videos?.videoUrl ?? '';
17 21 final String? _typeTitle;
18 22 String? get typeTitle => _typeTitle;
19 23 final String? _courseLessonId;
... ... @@ -21,12 +25,32 @@ class LookVideoBloc extends BaseSectionBloc&lt;BaseSectionEvent, BaseSectionState&gt;
21 25 final bool _isTopic;
22 26 bool get isTopic => _isTopic;
23 27  
  28 + CourseProcessEntity? _entity;
  29 +
  30 + CourseProcessEntity? get entity => _entity;
  31 +
24 32 LookVideoBloc(this._videoUrl, this._typeTitle, this._courseLessonId, this._isTopic) : super(LookVideoInitial()) {
25 33 on<LookVideoEvent>((event, emit) {
26 34 // TODO: implement event handler
27 35 });
  36 + on<RequestDataEvent>(_requestData);
28 37 on<SectionAgainEvent>((event, emit) {
29 38 emit(SectionAgainState());
30 39 });
31 40 }
  41 +
  42 + ///请求数据
  43 + void _requestData(
  44 + RequestDataEvent event, Emitter<BaseSectionState> emitter) async {
  45 + try {
  46 + await loading(() async {
  47 + _entity = await ListenDao.process(courseLessonId);
  48 + emitter(RequestDataState());
  49 + });
  50 + } catch (e) {
  51 + if (e is ApiException) {
  52 + showToast(e.message ?? '请求失败,请检查网络连接');
  53 + }
  54 + }
  55 + }
32 56 }
... ...
lib/pages/video/lookvideo/bloc/look_video_event.dart
... ... @@ -4,3 +4,5 @@ part of &#39;look_video_bloc.dart&#39;;
4 4 abstract class LookVideoEvent extends BaseSectionEvent {}
5 5  
6 6 class RestartVideoEvent extends LookVideoEvent {}
  7 +
  8 +class RequestDataEvent extends LookVideoEvent {}
... ...
lib/pages/video/lookvideo/bloc/look_video_state.dart
... ... @@ -6,3 +6,5 @@ abstract class LookVideoState extends BaseSectionState {}
6 6 class LookVideoInitial extends LookVideoState {}
7 7  
8 8 class VideoStarState extends LookVideoState {}
  9 +
  10 +class RequestDataState extends LookVideoState {}
... ...
lib/pages/video/lookvideo/look_video_page.dart
... ... @@ -3,10 +3,15 @@ import &#39;package:flutter_bloc/flutter_bloc.dart&#39;;
3 3 import 'package:wow_english/pages/section/subsection/base_section/state.dart';
4 4 import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart';
5 5 import 'package:wow_english/pages/video/lookvideo/widgets/video_widget.dart';
  6 +import 'package:wow_english/utils/log_util.dart';
6 7  
7 8 class LookVideoPage extends StatelessWidget {
8 9 const LookVideoPage(
9   - {super.key, this.videoUrl, this.typeTitle, this.courseLessonId, this.isTopic = false});
  10 + {super.key,
  11 + this.videoUrl,
  12 + this.typeTitle,
  13 + this.courseLessonId,
  14 + this.isTopic = false});
10 15  
11 16 final String? videoUrl;
12 17 final String? typeTitle;
... ... @@ -16,24 +21,34 @@ class LookVideoPage extends StatelessWidget {
16 21 @override
17 22 Widget build(BuildContext context) {
18 23 return BlocProvider(
19   - create: (BuildContext context) => LookVideoBloc(videoUrl, typeTitle, courseLessonId, isTopic),
  24 + create: (BuildContext context) =>
  25 + LookVideoBloc(videoUrl, typeTitle, courseLessonId, isTopic)
  26 + ..add(RequestDataEvent()),
20 27 child: Builder(builder: (context) => _buildPage(context)),
21 28 );
22 29 }
23 30 }
24 31  
25 32 Widget _buildPage(BuildContext context) {
26   - return BlocBuilder<LookVideoBloc, BaseSectionState>(builder: (context, state) {
27   - final bloc = BlocProvider.of<LookVideoBloc>(context);
28   - return Container(
29   - color: Colors.white,
30   - child: VideoWidget(
31   - videoUrl: bloc.videoUrl ?? '',
32   - typeTitle: bloc.typeTitle ?? '',
33   - courseLessonId: bloc.courseLessonId ?? '',
34   - isTopic: bloc.isTopic,
35   - )
36   - );
37   - }
38   - );
  33 + return BlocBuilder<LookVideoBloc, BaseSectionState>(
  34 + builder: (context, state) {
  35 + final bloc = BlocProvider.of<LookVideoBloc>(context);
  36 + Log.d("WQF lookvideo BlocBuilder bloc.videoUr=${bloc.videoUrl}");
  37 + return Center(
  38 + child: bloc.videoUrl?.isNotEmpty == true
  39 + ? Container(
  40 + color: Colors.white,
  41 + child: VideoWidget(
  42 + videoUrl: bloc.videoUrl ?? '',
  43 + typeTitle: bloc.typeTitle ?? '',
  44 + courseLessonId: bloc.courseLessonId ?? '',
  45 + isTopic: bloc.isTopic,
  46 + ))
  47 + //todo 空了需要抽一个通用的loading组件
  48 + : Container(
  49 + color: Colors.white,
  50 + child: const CircularProgressIndicator(),
  51 + ),
  52 + );
  53 + });
39 54 }
... ...
lib/pages/video/lookvideo/widgets/video_widget.dart
... ... @@ -12,11 +12,12 @@ import &#39;../../../section/subsection/base_section/state.dart&#39;;
12 12 import 'video_opera_widget.dart';
13 13  
14 14 class VideoWidget extends StatefulWidget {
15   - const VideoWidget({super.key,
16   - this.videoUrl = '',
17   - this.typeTitle,
18   - this.courseLessonId = '',
19   - this.isTopic = false});
  15 + const VideoWidget(
  16 + {super.key,
  17 + this.videoUrl = '',
  18 + this.typeTitle,
  19 + this.courseLessonId = '',
  20 + this.isTopic = false});
20 21  
21 22 final String videoUrl;
22 23 final String? typeTitle;
... ... @@ -39,10 +40,10 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
39 40  
40 41 String formatDuration(Duration duration) {
41 42 String hours = duration.inHours.toString().padLeft(2, '0');
42   - String minutes = duration.inMinutes.remainder(60).toString().padLeft(
43   - 2, '0');
44   - String seconds = duration.inSeconds.remainder(60).toString().padLeft(
45   - 2, '0');
  43 + String minutes =
  44 + duration.inMinutes.remainder(60).toString().padLeft(2, '0');
  45 + String seconds =
  46 + duration.inSeconds.remainder(60).toString().padLeft(2, '0');
46 47 return "$hours:$minutes:$seconds";
47 48 }
48 49  
... ... @@ -71,17 +72,17 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
71 72 _controller!.value.position.inSeconds ==
72 73 _controller!.value.duration.inSeconds) {
73 74 final lookVideoBloc = context.read<LookVideoBloc>();
74   - lookVideoBloc.sectionComplete(() {
75   - popPage(data: {
76   - 'courseLessonId': widget.courseLessonId,
77   - 'currentTime': getCurrentPositionSeconds(),
78   - 'isCompleted': true,
79   - 'nextSection': widget.isTopic
80   - });
81   - } as VoidCallback,
82   - againSectionTap: (() {
83   - lookVideoBloc.add(SectionAgainEvent());
84   - }), context: context);
  75 + lookVideoBloc.sectionComplete(
  76 + () {
  77 + popPage(data: {
  78 + 'courseLessonId': widget.courseLessonId,
  79 + 'currentTime': getCurrentPositionSeconds(),
  80 + 'isCompleted': true,
  81 + 'nextSection': widget.isTopic
  82 + });
  83 + } as VoidCallback, againSectionTap: (() {
  84 + lookVideoBloc.add(SectionAgainEvent());
  85 + }), context: context);
85 86 }
86 87 }
87 88 });
... ... @@ -189,63 +190,63 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
189 190 child: Center(
190 191 child: _controller!.value.isInitialized
191 192 ? Stack(
192   - alignment: Alignment.center,
193   - children: [
194   - SizedBox(
195   - height: double.infinity,
196   - width: double.infinity,
197   - child: AspectRatio(
198   - aspectRatio: _controller!.value.aspectRatio,
199   - child: VideoPlayer(_controller!),
200   - ),
201   - ),
202   - Offstage(
203   - offstage: _hiddenTipView,
204   - child: VideoOperaWidget(
205   - title: widget.typeTitle ?? 'song',
206   - degree: _playDegree,
207   - totalTime: _totalTime,
208   - currentTime: _currentTime,
209   - isPlay: _controller!.value.isPlaying,
210   - actionEvent: (OperationType type) {
211   - actionType(type);
212   - },
213   - sliderChangeEvent: (double degree) {
214   - int totalSecond = _controller!
215   - .value.duration.inMinutes
216   - .remainder(60) *
217   - 60 +
218   - _controller!.value.duration.inSeconds
219   - .remainder(60);
220   - int positionSecond = (totalSecond * degree).toInt();
221   - _controller!
222   - .seekTo(Duration(seconds: positionSecond));
223   - },
224   - ),
225   - ),
226   - Offstage(
227   - offstage: _controller!.value.isPlaying,
228   - child: IconButton(
229   - onPressed: () {
230   - _controller!.play();
231   - },
232   - icon: Image.asset(
233   - 'video_stop'.assetPng,
234   - width: 70.w,
235   - height: 70.h,
236   - ),
237   - ),
238   - )
239   - ],
240   - )
  193 + alignment: Alignment.center,
  194 + children: [
  195 + SizedBox(
  196 + height: double.infinity,
  197 + width: double.infinity,
  198 + child: AspectRatio(
  199 + aspectRatio: _controller!.value.aspectRatio,
  200 + child: VideoPlayer(_controller!),
  201 + ),
  202 + ),
  203 + Offstage(
  204 + offstage: _hiddenTipView,
  205 + child: VideoOperaWidget(
  206 + title: widget.typeTitle ?? 'song',
  207 + degree: _playDegree,
  208 + totalTime: _totalTime,
  209 + currentTime: _currentTime,
  210 + isPlay: _controller!.value.isPlaying,
  211 + actionEvent: (OperationType type) {
  212 + actionType(type);
  213 + },
  214 + sliderChangeEvent: (double degree) {
  215 + int totalSecond = _controller!
  216 + .value.duration.inMinutes
  217 + .remainder(60) *
  218 + 60 +
  219 + _controller!.value.duration.inSeconds
  220 + .remainder(60);
  221 + int positionSecond = (totalSecond * degree).toInt();
  222 + _controller!
  223 + .seekTo(Duration(seconds: positionSecond));
  224 + },
  225 + ),
  226 + ),
  227 + Offstage(
  228 + offstage: _controller!.value.isPlaying,
  229 + child: IconButton(
  230 + onPressed: () {
  231 + _controller!.play();
  232 + },
  233 + icon: Image.asset(
  234 + 'video_stop'.assetPng,
  235 + width: 70.w,
  236 + height: 70.h,
  237 + ),
  238 + ),
  239 + )
  240 + ],
  241 + )
241 242 : Container(
242   - color: Colors.white,
243   - child: const CircularProgressIndicator(),
244   - // Text(
245   - // '视频加载中....',
246   - // style: TextStyle(fontSize: 20.sp, color: Colors.black),
247   - // ),
248   - ),
  243 + color: Colors.white,
  244 + child: const CircularProgressIndicator(),
  245 + // Text(
  246 + // '视频加载中....',
  247 + // style: TextStyle(fontSize: 20.sp, color: Colors.black),
  248 + // ),
  249 + ),
249 250 ),
250 251 ),
251 252 );
... ...