Commit 46675a89f53e32a1b709d8512fbff8971f33ead9
1 parent
e3a0f013
feat:过渡页-视频环节
Showing
20 changed files
with
266 additions
and
42 deletions
assets/images/section_finish_again.png
0 → 100644
17.3 KB
assets/images/section_finish_next.png
0 → 100644
13.8 KB
assets/images/section_finish_steve.png
0 → 100644
34.7 KB
lib/common/request/apis.dart
@@ -89,9 +89,12 @@ class Apis { | @@ -89,9 +89,12 @@ class Apis { | ||
89 | /// 进入课堂 | 89 | /// 进入课堂 |
90 | static const String enterClass = 'course/enter/class'; | 90 | static const String enterClass = 'course/enter/class'; |
91 | 91 | ||
92 | - /// 退出课堂 | 92 | + /// 退出课堂(非完整、中断) |
93 | static const String exitClass = 'course/exit/class'; | 93 | static const String exitClass = 'course/exit/class'; |
94 | 94 | ||
95 | + /// 结束课堂(完整) | ||
96 | + static const String endClass = 'course/end/class'; | ||
97 | + | ||
95 | /// 商品列表 | 98 | /// 商品列表 |
96 | static const String productList = 'order/course/combo/list'; | 99 | static const String productList = 'order/course/combo/list'; |
97 | 100 |
lib/common/request/dao/listen_dao.dart
@@ -4,6 +4,7 @@ import 'package:wow_english/models/follow_read_entity.dart'; | @@ -4,6 +4,7 @@ import 'package:wow_english/models/follow_read_entity.dart'; | ||
4 | import 'package:wow_english/models/listen_entity.dart'; | 4 | import 'package:wow_english/models/listen_entity.dart'; |
5 | 5 | ||
6 | import '../../../models/read_content_entity.dart'; | 6 | import '../../../models/read_content_entity.dart'; |
7 | +import '../../../utils/date_util.dart'; | ||
7 | 8 | ||
8 | class ListenDao { | 9 | class ListenDao { |
9 | /// 磨耳朵 | 10 | /// 磨耳朵 |
@@ -43,8 +44,14 @@ class ListenDao { | @@ -43,8 +44,14 @@ class ListenDao { | ||
43 | } | 44 | } |
44 | 45 | ||
45 | ///退出课堂 | 46 | ///退出课堂 |
46 | - static Future exitClass(courseLessonId,currentStep,currentTime) async { | ||
47 | - var data = await requestClient.post(Apis.exitClass,data: {'courseLessonId':courseLessonId,'currentStep':currentStep,'currentTime':currentTime}); | 47 | + static Future exitClass(courseLessonId,currentStep,{int? currentTime}) async { |
48 | + var data = await requestClient.post(Apis.exitClass,data: {'courseLessonId':courseLessonId,'currentStep':currentStep,'currentTime':currentTime ?? getTimestampOfSecond()}); | ||
49 | + return data; | ||
50 | + } | ||
51 | + | ||
52 | + ///完成课堂 | ||
53 | + static Future endClass(courseLessonId,currentStep,{int? currentTime}) async { | ||
54 | + var data = await requestClient.post(Apis.endClass,data: {'courseLessonId':courseLessonId,'currentStep':currentStep,'currentTime':currentTime ?? getTimestampOfSecond()}); | ||
48 | return data; | 55 | return data; |
49 | } | 56 | } |
50 | } | 57 | } |
lib/pages/section/bloc/section_bloc.dart
@@ -49,6 +49,7 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -49,6 +49,7 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
49 | SectionBloc(this._courseUnitEntity, this._courseUnitDetail, this._pageController, this._listController) : super(LessonInitial()) { | 49 | SectionBloc(this._courseUnitEntity, this._courseUnitDetail, this._pageController, this._listController) : super(LessonInitial()) { |
50 | on<RequestDataEvent>(_requestData); | 50 | on<RequestDataEvent>(_requestData); |
51 | on<RequestExitClassEvent>(_requestExitClass); | 51 | on<RequestExitClassEvent>(_requestExitClass); |
52 | + on<RequestEndClassEvent>(_requestEndClass); | ||
52 | on<RequestEnterClassEvent>(_requestEnterClass); | 53 | on<RequestEnterClassEvent>(_requestEnterClass); |
53 | on<RequestVideoLessonEvent>(_requestVideoLesson); | 54 | on<RequestVideoLessonEvent>(_requestVideoLesson); |
54 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); | 55 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); |
@@ -95,7 +96,16 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -95,7 +96,16 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
95 | } | 96 | } |
96 | 97 | ||
97 | void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { | 98 | void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { |
98 | - await ListenDao.exitClass(event.courseLessonId,event.currentStep,event.currentTime); | 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); | ||
104 | + if (event.autoNextSection) { | ||
105 | + final nextCourseSection = getNextCourseSectionBySort(int.parse(event.courseLessonId)); | ||
106 | + ///进入课堂 | ||
107 | + add(RequestEnterClassEvent(nextCourseSection!.id.toString() ?? '', nextCourseSection.courseType)); | ||
108 | + } | ||
99 | } | 109 | } |
100 | 110 | ||
101 | void _pageControllerChange(CurrentUnitIndexChangeEvent event, | 111 | void _pageControllerChange(CurrentUnitIndexChangeEvent event, |
@@ -108,4 +118,29 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -108,4 +118,29 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
108 | int unlockPageCount() { | 118 | int unlockPageCount() { |
109 | return _courseUnitEntity.courseUnitVOList?.indexWhereOrNull((element) => element.lock == true) ?? 1; | 119 | return _courseUnitEntity.courseUnitVOList?.indexWhereOrNull((element) => element.lock == true) ?? 1; |
110 | } | 120 | } |
121 | + | ||
122 | + CourseSectionEntity? getNextCourseSectionBySort(int courseLessonId) { | ||
123 | + final curCourseSectionEntity = _courseSectionDatas?.firstWhere((element) => element.id == courseLessonId); | ||
124 | + final curSort = curCourseSectionEntity?.sortOrder ?? 0; | ||
125 | + CourseSectionEntity? nextCourseSectionEntity = _courseSectionDatas?.firstWhere((element) => element.sortOrder == curSort + 1); | ||
126 | + if (nextCourseSectionEntity != null) { | ||
127 | + return nextCourseSectionEntity; | ||
128 | + } else { | ||
129 | + ///跨unit选lesson | ||
130 | + final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList?.firstWhere((element) => element.id == curCourseSectionEntity?.courseUnitId); | ||
131 | + if (curCourseUnitDetail != null) { | ||
132 | + final nextCourseUnitDetail = _courseUnitEntity.courseUnitVOList?.firstWhere((element) => element.sortOrder == 0); | ||
133 | + if (nextCourseUnitDetail != null) { | ||
134 | + ///pageView翻页了,可能需要预加载 todo | ||
135 | + return null; | ||
136 | + } else { | ||
137 | + ///最后一个unit了 | ||
138 | + return null; | ||
139 | + } | ||
140 | + } else { | ||
141 | + ///找不到对应的unitDetail,理论上不可能 | ||
142 | + return null; | ||
143 | + } | ||
144 | + } | ||
145 | + } | ||
111 | } | 146 | } |
lib/pages/section/bloc/section_event.dart
@@ -27,6 +27,16 @@ class RequestExitClassEvent extends SectionEvent { | @@ -27,6 +27,16 @@ class RequestExitClassEvent extends SectionEvent { | ||
27 | RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); | 27 | RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); |
28 | } | 28 | } |
29 | 29 | ||
30 | +///结束课堂 | ||
31 | +class RequestEndClassEvent extends SectionEvent { | ||
32 | + final String courseLessonId; | ||
33 | + final String currentStep; | ||
34 | + final String currentTime; | ||
35 | + ///自动进入下一环节 | ||
36 | + final bool autoNextSection; | ||
37 | + RequestEndClassEvent(this.courseLessonId,this.currentStep,this.currentTime,{this.autoNextSection = false}); | ||
38 | +} | ||
39 | + | ||
30 | ///页面切换 | 40 | ///页面切换 |
31 | class CurrentUnitIndexChangeEvent extends SectionEvent { | 41 | class CurrentUnitIndexChangeEvent extends SectionEvent { |
32 | final int unitIndex; | 42 | final int unitIndex; |
lib/pages/section/section_page.dart
@@ -6,6 +6,7 @@ import 'package:nested_scroll_views/material.dart'; | @@ -6,6 +6,7 @@ import 'package:nested_scroll_views/material.dart'; | ||
6 | import 'package:wow_english/common/core/user_util.dart'; | 6 | import 'package:wow_english/common/core/user_util.dart'; |
7 | import 'package:wow_english/common/extension/string_extension.dart'; | 7 | import 'package:wow_english/common/extension/string_extension.dart'; |
8 | import 'package:wow_english/models/course_unit_entity.dart'; | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
9 | +import 'package:wow_english/pages/section/section_type.dart'; | ||
9 | import 'package:wow_english/pages/section/widgets/home_video_item.dart'; | 10 | import 'package:wow_english/pages/section/widgets/home_video_item.dart'; |
10 | import 'package:wow_english/pages/section/widgets/section_bouns_item.dart'; | 11 | import 'package:wow_english/pages/section/widgets/section_bouns_item.dart'; |
11 | import 'package:wow_english/pages/section/widgets/section_header_widget.dart'; | 12 | import 'package:wow_english/pages/section/widgets/section_header_widget.dart'; |
@@ -52,15 +53,15 @@ class _SectionPageView extends StatelessWidget { | @@ -52,15 +53,15 @@ class _SectionPageView extends StatelessWidget { | ||
52 | if (state is RequestVideoLessonState) { | 53 | if (state is RequestVideoLessonState) { |
53 | final videoUrl = bloc.processEntity?.videos?.videoUrl ?? ''; | 54 | final videoUrl = bloc.processEntity?.videos?.videoUrl ?? ''; |
54 | var title = ''; | 55 | var title = ''; |
55 | - if (state.type == 1) { | 56 | + if (state.type == SectionType.song.value) { |
56 | title = 'song'; | 57 | title = 'song'; |
57 | } | 58 | } |
58 | 59 | ||
59 | - if (state.type == 2) { | 60 | + if (state.type == SectionType.video.value) { |
60 | title = 'video'; | 61 | title = 'video'; |
61 | } | 62 | } |
62 | 63 | ||
63 | - if (state.type == 5) { | 64 | + if (state.type == SectionType.bouns.value) { |
64 | title = 'bonus'; | 65 | title = 'bonus'; |
65 | } | 66 | } |
66 | 67 | ||
@@ -70,22 +71,21 @@ class _SectionPageView extends StatelessWidget { | @@ -70,22 +71,21 @@ class _SectionPageView extends StatelessWidget { | ||
70 | pushNamed(AppRouteName.lookVideo, arguments: { | 71 | pushNamed(AppRouteName.lookVideo, arguments: { |
71 | 'videoUrl': videoUrl, | 72 | 'videoUrl': videoUrl, |
72 | 'title': title, | 73 | 'title': title, |
73 | - 'courseLessonId': state.courseLessonId | 74 | + 'courseLessonId': state.courseLessonId, |
75 | + 'isTopic': true | ||
74 | }).then((value) { | 76 | }).then((value) { |
75 | if (value != null) { | 77 | if (value != null) { |
76 | - Map<String, String> dataMap = value as Map<String, String>; | ||
77 | - bloc.add(RequestExitClassEvent( | ||
78 | - dataMap['courseLessonId']!, | ||
79 | - '0', | ||
80 | - dataMap['currentTime']!, | ||
81 | - )); | 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)); | ||
82 | } | 81 | } |
83 | }); | 82 | }); |
84 | return; | 83 | return; |
85 | } | 84 | } |
86 | 85 | ||
87 | if (state is RequestEnterClassState) { | 86 | if (state is RequestEnterClassState) { |
88 | - if (state.courseType != 3 && state.courseType != 4) { | 87 | + if (state.courseType != SectionType.practice.value |
88 | + && state.courseType != SectionType.pictureBook.value) { | ||
89 | ///视频类型 | 89 | ///视频类型 |
90 | ///获取视频课程内容 | 90 | ///获取视频课程内容 |
91 | bloc.add(RequestVideoLessonEvent( | 91 | bloc.add(RequestVideoLessonEvent( |
@@ -93,7 +93,7 @@ class _SectionPageView extends StatelessWidget { | @@ -93,7 +93,7 @@ class _SectionPageView extends StatelessWidget { | ||
93 | return; | 93 | return; |
94 | } | 94 | } |
95 | 95 | ||
96 | - if (state.courseType == 4) { | 96 | + if (state.courseType == SectionType.pictureBook.value) { |
97 | //绘本 | 97 | //绘本 |
98 | pushNamed(AppRouteName.reading, | 98 | pushNamed(AppRouteName.reading, |
99 | arguments: {'courseLessonId': state.courseLessonId}) | 99 | arguments: {'courseLessonId': state.courseLessonId}) |
@@ -107,7 +107,7 @@ class _SectionPageView extends StatelessWidget { | @@ -107,7 +107,7 @@ class _SectionPageView extends StatelessWidget { | ||
107 | return; | 107 | return; |
108 | } | 108 | } |
109 | 109 | ||
110 | - if (state.courseType == 3) { | 110 | + if (state.courseType == SectionType.practice.value) { |
111 | //练习 | 111 | //练习 |
112 | pushNamed(AppRouteName.topicPic, | 112 | pushNamed(AppRouteName.topicPic, |
113 | arguments: {'courseLessonId': state.courseLessonId}) | 113 | arguments: {'courseLessonId': state.courseLessonId}) |
@@ -214,7 +214,7 @@ Widget _itemTransCard(int index, BuildContext context) { | @@ -214,7 +214,7 @@ Widget _itemTransCard(int index, BuildContext context) { | ||
214 | scrollDirection: Axis.horizontal, | 214 | scrollDirection: Axis.horizontal, |
215 | itemBuilder: (BuildContext context, int index) { | 215 | itemBuilder: (BuildContext context, int index) { |
216 | CourseSectionEntity sectionData = bloc.courseSectionDatas![index]; | 216 | CourseSectionEntity sectionData = bloc.courseSectionDatas![index]; |
217 | - if (sectionData.courseType == 5) { | 217 | + if (sectionData.courseType == SectionType.bouns.value) { |
218 | //彩蛋 | 218 | //彩蛋 |
219 | return GestureDetector( | 219 | return GestureDetector( |
220 | onTap: () { | 220 | onTap: () { |
lib/pages/section/section_type.dart
0 → 100644
1 | +///环节类型 | ||
2 | +enum SectionType { | ||
3 | + ///儿歌 | ||
4 | + song, | ||
5 | + | ||
6 | + ///视频 | ||
7 | + video, | ||
8 | + | ||
9 | + ///练习 | ||
10 | + practice, | ||
11 | + | ||
12 | + ///绘本 | ||
13 | + pictureBook, | ||
14 | + | ||
15 | + ///彩蛋 | ||
16 | + bouns | ||
17 | +} | ||
18 | + | ||
19 | +extension SectionTypeExtension on SectionType { | ||
20 | + int get value { | ||
21 | + switch (this) { | ||
22 | + case SectionType.song: | ||
23 | + return 1; | ||
24 | + case SectionType.video: | ||
25 | + return 2; | ||
26 | + case SectionType.practice: | ||
27 | + return 3; | ||
28 | + case SectionType.pictureBook: | ||
29 | + return 4; | ||
30 | + case SectionType.bouns: | ||
31 | + return 5; | ||
32 | + default: | ||
33 | + throw ArgumentError('Unknown section type'); | ||
34 | + } | ||
35 | + } | ||
36 | +} |
lib/pages/section/subsection/base_section/bloc.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | ||
3 | +import 'package:wow_english/common/extension/string_extension.dart'; | ||
4 | + | ||
5 | +import '../../../../route/route.dart'; | ||
6 | +import 'event.dart'; | ||
7 | +import 'state.dart'; | ||
8 | + | ||
9 | +abstract class BaseSectionBloc<E extends BaseSectionEvent, | ||
10 | + S extends BaseSectionState> extends Bloc<E, S> { | ||
11 | + BaseSectionBloc(super.initialState); | ||
12 | + | ||
13 | + bool isCompleteDialogShow = false; | ||
14 | + | ||
15 | + // 这里可以定义一些通用的逻辑 | ||
16 | + void completeSection(final VoidCallback? nextSectionTap) { | ||
17 | + // 逻辑来标记步骤为已完成 | ||
18 | + // 比如更新状态 | ||
19 | + if (isCompleteDialogShow) { | ||
20 | + return; | ||
21 | + } | ||
22 | + isCompleteDialogShow = true; | ||
23 | + showDialog( | ||
24 | + context: AppRouter.context, | ||
25 | + barrierDismissible: false, | ||
26 | + barrierColor: Colors.black54, | ||
27 | + builder: (BuildContext context) { | ||
28 | + return AlertDialog( | ||
29 | + backgroundColor: Colors.transparent, | ||
30 | + content: SizedBox( | ||
31 | + width: double.infinity, // 宽度设置为无限,使其尽可能铺满屏幕 | ||
32 | + height: MediaQuery.of(context).size.height * 0.6, // 高度设置为屏幕高度的60% | ||
33 | + child: Row( | ||
34 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, // 图片之间分配空间 | ||
35 | + children: <Widget>[ | ||
36 | + // 左侧可点击的图片 | ||
37 | + Expanded( | ||
38 | + flex: 1, | ||
39 | + child: GestureDetector( | ||
40 | + onTap: () { | ||
41 | + isCompleteDialogShow = false; | ||
42 | + popPage(); | ||
43 | + add(SectionAgainEvent() as E); | ||
44 | + }, | ||
45 | + child: Image.asset('section_finish_again'.assetPng), | ||
46 | + ), | ||
47 | + ), | ||
48 | + // 中间的图片 | ||
49 | + Expanded( | ||
50 | + flex: 2, | ||
51 | + child: Image.asset('section_finish_steve'.assetPng), | ||
52 | + ), | ||
53 | + // 右侧可点击的图片 | ||
54 | + Expanded( | ||
55 | + flex: 1, | ||
56 | + child: GestureDetector( | ||
57 | + onTap: () { | ||
58 | + // 处理右侧图片的点击事件 | ||
59 | + isCompleteDialogShow = false; | ||
60 | + popPage(); | ||
61 | + nextSectionTap!(); | ||
62 | + }, | ||
63 | + child: Image.asset('section_finish_next'.assetPng), | ||
64 | + ), | ||
65 | + ), | ||
66 | + ], | ||
67 | + ), | ||
68 | + ), | ||
69 | + ); | ||
70 | + }, | ||
71 | + ); | ||
72 | + } | ||
73 | +} |
lib/pages/section/subsection/base_section/event.dart
0 → 100644
1 | +abstract class BaseSectionEvent {} | ||
2 | + | ||
3 | +///环节完成(结束) | ||
4 | +class SectionCompleted extends BaseSectionEvent {} | ||
5 | + | ||
6 | +///环节再来一次 | ||
7 | +class SectionAgainEvent extends BaseSectionEvent {} | ||
8 | + | ||
9 | +///下一个环节 | ||
10 | +class SectionNextEvent extends BaseSectionEvent {} | ||
0 | \ No newline at end of file | 11 | \ No newline at end of file |
lib/pages/section/subsection/base_section/state.dart
0 → 100644
lib/pages/video/lookvideo/bloc/look_video_bloc.dart
1 | import 'package:flutter/cupertino.dart'; | 1 | import 'package:flutter/cupertino.dart'; |
2 | import 'package:flutter_bloc/flutter_bloc.dart'; | 2 | import 'package:flutter_bloc/flutter_bloc.dart'; |
3 | import 'package:video_player/video_player.dart'; | 3 | import 'package:video_player/video_player.dart'; |
4 | +import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; | ||
5 | +import 'package:wow_english/pages/section/subsection/base_section/event.dart'; | ||
6 | +import 'package:wow_english/pages/section/subsection/base_section/state.dart'; | ||
4 | 7 | ||
5 | part 'look_video_event.dart'; | 8 | part 'look_video_event.dart'; |
6 | part 'look_video_state.dart'; | 9 | part 'look_video_state.dart'; |
7 | 10 | ||
8 | -class LookVideoBloc extends Bloc<LookVideoEvent, LookVideoState> { | 11 | +class LookVideoBloc extends BaseSectionBloc<LookVideoEvent, LookVideoState> { |
9 | 12 | ||
10 | VideoPlayerController? _controller; | 13 | VideoPlayerController? _controller; |
11 | 14 | ||
12 | - LookVideoBloc() : super(LookVideoInitial()) { | 15 | + final String? _videoUrl; |
16 | + String? get videoUrl => _videoUrl; | ||
17 | + final String? _typeTitle; | ||
18 | + String? get typeTitle => _typeTitle; | ||
19 | + final String? _courseLessonId; | ||
20 | + String? get courseLessonId => _courseLessonId; | ||
21 | + final bool _isTopic; | ||
22 | + bool get isTopic => _isTopic; | ||
23 | + | ||
24 | + LookVideoBloc(this._videoUrl, this._typeTitle, this._courseLessonId, this._isTopic) : super(LookVideoInitial()) { | ||
13 | on<LookVideoEvent>((event, emit) { | 25 | on<LookVideoEvent>((event, emit) { |
14 | // TODO: implement event handler | 26 | // TODO: implement event handler |
15 | }); | 27 | }); |
lib/pages/video/lookvideo/bloc/look_video_event.dart
lib/pages/video/lookvideo/bloc/look_video_state.dart
1 | part of 'look_video_bloc.dart'; | 1 | part of 'look_video_bloc.dart'; |
2 | 2 | ||
3 | @immutable | 3 | @immutable |
4 | -abstract class LookVideoState {} | 4 | +abstract class LookVideoState extends BaseSectionState {} |
5 | 5 | ||
6 | class LookVideoInitial extends LookVideoState {} | 6 | class LookVideoInitial extends LookVideoState {} |
7 | 7 |
lib/pages/video/lookvideo/look_video_page.dart
1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | ||
3 | +import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart'; | ||
2 | import 'package:wow_english/pages/video/lookvideo/widgets/video_widget.dart'; | 4 | import 'package:wow_english/pages/video/lookvideo/widgets/video_widget.dart'; |
3 | 5 | ||
4 | -class LookVideoPage extends StatefulWidget { | ||
5 | - const LookVideoPage({super.key, this.videoUrl, this.typeTitle, this.courseLessonId}); | 6 | +class LookVideoPage extends StatelessWidget { |
7 | + const LookVideoPage( | ||
8 | + {super.key, this.videoUrl, this.typeTitle, this.courseLessonId, this.isTopic = false}); | ||
6 | 9 | ||
7 | final String? videoUrl; | 10 | final String? videoUrl; |
8 | final String? typeTitle; | 11 | final String? typeTitle; |
9 | final String? courseLessonId; | 12 | final String? courseLessonId; |
13 | + final bool isTopic; | ||
10 | 14 | ||
11 | @override | 15 | @override |
12 | - State<StatefulWidget> createState() { | ||
13 | - return _LookVideoPageState(); | ||
14 | - } | ||
15 | -} | ||
16 | - | ||
17 | -class _LookVideoPageState extends State<LookVideoPage> { | ||
18 | - @override | ||
19 | Widget build(BuildContext context) { | 16 | Widget build(BuildContext context) { |
20 | - return Container( | ||
21 | - color: Colors.white, | ||
22 | - child: VideoWidget( | ||
23 | - videoUrl: widget.videoUrl??'', | ||
24 | - typeTitle: widget.typeTitle, | ||
25 | - courseLessonId: widget.courseLessonId??'', | ||
26 | - ), | 17 | + return BlocProvider( |
18 | + create: (BuildContext context) => LookVideoBloc(videoUrl, typeTitle, courseLessonId, isTopic), | ||
19 | + child: Builder(builder: (context) => _buildPage(context)), | ||
27 | ); | 20 | ); |
28 | } | 21 | } |
29 | -} | ||
30 | \ No newline at end of file | 22 | \ No newline at end of file |
23 | +} | ||
24 | + | ||
25 | +Widget _buildPage(BuildContext context) { | ||
26 | + return BlocBuilder<LookVideoBloc, LookVideoState>(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 | + ); | ||
39 | +} |
lib/pages/video/lookvideo/widgets/video_widget.dart
1 | import 'package:common_utils/common_utils.dart'; | 1 | import 'package:common_utils/common_utils.dart'; |
2 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
3 | +import 'package:flutter_bloc/flutter_bloc.dart'; | ||
3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
4 | import 'package:video_player/video_player.dart'; | 5 | import 'package:video_player/video_player.dart'; |
5 | import 'package:wow_english/common/extension/string_extension.dart'; | 6 | import 'package:wow_english/common/extension/string_extension.dart'; |
7 | +import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart'; | ||
6 | import 'package:wow_english/route/route.dart'; | 8 | import 'package:wow_english/route/route.dart'; |
7 | 9 | ||
8 | import 'video_opera_widget.dart'; | 10 | import 'video_opera_widget.dart'; |
9 | 11 | ||
10 | class VideoWidget extends StatefulWidget { | 12 | class VideoWidget extends StatefulWidget { |
11 | - const VideoWidget({super.key, this.videoUrl = '',this.typeTitle, this.courseLessonId = ''}); | 13 | + const VideoWidget({super.key, this.videoUrl = '',this.typeTitle, this.courseLessonId = '', this.isTopic = false}); |
12 | 14 | ||
13 | final String videoUrl; | 15 | final String videoUrl; |
14 | final String? typeTitle; | 16 | final String? typeTitle; |
15 | final String courseLessonId; | 17 | final String courseLessonId; |
18 | + final bool isTopic; | ||
16 | 19 | ||
17 | @override | 20 | @override |
18 | State<StatefulWidget> createState() { | 21 | State<StatefulWidget> createState() { |
@@ -51,6 +54,12 @@ class _VideoWidgetState extends State<VideoWidget> { | @@ -51,6 +54,12 @@ class _VideoWidgetState extends State<VideoWidget> { | ||
51 | _playDegree = 0.0; | 54 | _playDegree = 0.0; |
52 | } | 55 | } |
53 | }); | 56 | }); |
57 | + } else if (_controller!.value.isCompleted) { | ||
58 | + context.read<LookVideoBloc>().completeSection((){ | ||
59 | + String currentTime = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toString(); | ||
60 | + popPage(data:{'courseLessonId':widget.courseLessonId,'currentTime':currentTime, | ||
61 | + 'nextSection':widget.isTopic}); | ||
62 | + } as VoidCallback); | ||
54 | } | 63 | } |
55 | } | 64 | } |
56 | }); | 65 | }); |
@@ -114,7 +123,7 @@ class _VideoWidgetState extends State<VideoWidget> { | @@ -114,7 +123,7 @@ class _VideoWidgetState extends State<VideoWidget> { | ||
114 | setState(() { | 123 | setState(() { |
115 | _currentTime = formatDuration(_controller!.value.position); | 124 | _currentTime = formatDuration(_controller!.value.position); |
116 | _totalTime = formatDuration(_controller!.value.duration); | 125 | _totalTime = formatDuration(_controller!.value.duration); |
117 | - _controller!.setLooping(true); | 126 | + _controller!.setLooping(!widget.isTopic); |
118 | _controller!.setVolume(100); | 127 | _controller!.setVolume(100); |
119 | _controller!.play(); | 128 | _controller!.play(); |
120 | }); | 129 | }); |
lib/route/route.dart
@@ -190,11 +190,14 @@ class AppRouter { | @@ -190,11 +190,14 @@ class AppRouter { | ||
190 | final title = (settings.arguments as Map)['title'] as String?; | 190 | final title = (settings.arguments as Map)['title'] as String?; |
191 | final courseLessonId = | 191 | final courseLessonId = |
192 | (settings.arguments as Map)['courseLessonId'] as String?; | 192 | (settings.arguments as Map)['courseLessonId'] as String?; |
193 | + ///是否是课程内的视频环节,用于播放结束判断要不要再来一次以及下一环节用 | ||
194 | + final isTopic = (settings.arguments as Map)['isTopic'] as bool? ?? false; | ||
193 | return CupertinoPageRoute( | 195 | return CupertinoPageRoute( |
194 | builder: (_) => LookVideoPage( | 196 | builder: (_) => LookVideoPage( |
195 | videoUrl: videoUrl, | 197 | videoUrl: videoUrl, |
196 | typeTitle: title, | 198 | typeTitle: title, |
197 | courseLessonId: courseLessonId, | 199 | courseLessonId: courseLessonId, |
200 | + isTopic: isTopic, | ||
198 | )); | 201 | )); |
199 | /*case AppRouteName.setPwd: | 202 | /*case AppRouteName.setPwd: |
200 | case AppRouteName.setPwd: | 203 | case AppRouteName.setPwd: |
lib/utils/date_util.dart
0 → 100644
1 | + | ||
2 | +///获取当前时间(单位:秒) | ||
3 | +int getTimestampOfSecond() { | ||
4 | + // 获取当前时间 | ||
5 | + DateTime now = DateTime.now(); | ||
6 | + | ||
7 | + // 获取自Unix纪元以来的毫秒数 | ||
8 | + int milliseconds = now.millisecondsSinceEpoch; | ||
9 | + | ||
10 | + // 将毫秒数转换为秒 | ||
11 | + int seconds = milliseconds ~/ 1000; | ||
12 | + | ||
13 | + return seconds; | ||
14 | +} | ||
0 | \ No newline at end of file | 15 | \ No newline at end of file |
pubspec.yaml
@@ -90,7 +90,7 @@ dependencies: | @@ -90,7 +90,7 @@ dependencies: | ||
90 | # 富文本插件 https://pub.dev/packages/extended_text | 90 | # 富文本插件 https://pub.dev/packages/extended_text |
91 | extended_text: ^11.0.1 | 91 | extended_text: ^11.0.1 |
92 | # 视频播放 https://pub.dev/packages/video_player | 92 | # 视频播放 https://pub.dev/packages/video_player |
93 | - video_player: ^2.7.0 | 93 | + video_player: ^2.8.6 |
94 | # UI适配 https://pub.dev/packages/responsive_framework | 94 | # UI适配 https://pub.dev/packages/responsive_framework |
95 | responsive_framework: ^1.0.0 | 95 | responsive_framework: ^1.0.0 |
96 | # 音频播放 https://pub.dev/packages/audioplayers | 96 | # 音频播放 https://pub.dev/packages/audioplayers |