diff --git a/lib/common/request/dao/listen_dao.dart b/lib/common/request/dao/listen_dao.dart index 1808fea..6aeb4b4 100644 --- a/lib/common/request/dao/listen_dao.dart +++ b/lib/common/request/dao/listen_dao.dart @@ -15,43 +15,66 @@ class ListenDao { ///视频跟读 static Future?> followRead() async { - var data = await requestClient.get>(Apis.followRead); + var data = + await requestClient.get>(Apis.followRead); return data; } ///课程内容 static Future process(courseLessonId) async { - var data = await requestClient.get(Apis.process,queryParameters: {'courseLessonId':courseLessonId}); + var data = await requestClient.get(Apis.process, + queryParameters: {'courseLessonId': courseLessonId}); return data; } ///获取视频跟读内容 - static Future?> readContent(videoFollowReadId) async { - var data = await requestClient.get>(Apis.readContent,queryParameters: {'videoFollowReadId':videoFollowReadId}); + static Future?> readContent( + videoFollowReadId) async { + var data = await requestClient.get>( + Apis.readContent, + queryParameters: {'videoFollowReadId': videoFollowReadId}); return data; } ///视频跟读提交结果 - static Future followResult(frequency,videoFollowReadId) async { - var data = await requestClient.post(Apis.followResult,data: {'frequency':frequency,'videoFollowReadContentId':videoFollowReadId}); + static Future followResult(frequency, videoFollowReadId) async { + var data = await requestClient.post(Apis.followResult, data: { + 'frequency': frequency, + 'videoFollowReadContentId': videoFollowReadId + }); return data; } ///进入课堂 static Future enterClass(courseLessonId) async { - var data = await requestClient.post(Apis.enterClass,data: {'courseLessonId':courseLessonId}); + var data = await requestClient + .post(Apis.enterClass, data: {'courseLessonId': courseLessonId}); return data; } ///退出课堂 - static Future exitClass(courseLessonId,currentStep,{int? currentTime}) async { - var data = await requestClient.post(Apis.exitClass,data: {'courseLessonId':courseLessonId,'currentStep':currentStep,'currentTime':currentTime ?? getTimestampOfSecond()}); + static Future exitClass(courseLessonId, + {int? currentStep, int? currentTime}) async { + var data = await requestClient.post(Apis.exitClass, data: { + 'courseLessonId': courseLessonId, + + ///如果currentStep不为空,才传currentStep参数 + if (currentStep != null) 'currentStep': currentStep, + + ///如果currentTime不为空,才传currentTime参数 + if (currentTime != null) 'currentTime': currentTime + }); return data; } ///完成课堂 - static Future endClass(courseLessonId,currentStep,{int? currentTime}) async { - var data = await requestClient.post(Apis.endClass,data: {'courseLessonId':courseLessonId,'currentStep':currentStep,'currentTime':currentTime ?? getTimestampOfSecond()}); + static Future endClass(courseLessonId, + {int? currentStep, int? currentTime}) async { + var data = await requestClient.post(Apis.endClass, data: { + 'courseLessonId': courseLessonId, + if (currentStep != null) 'currentStep': currentStep, + if (currentTime != null) 'currentTime': currentTime + }); return data; } } diff --git a/lib/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index f422502..d84f1ce 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -326,9 +326,9 @@ class TopicPictureBloc extends BaseSectionBloc { : Container( color: Colors.white, - child: Text( - '视频加载中....', - style: TextStyle( - fontSize: 20.sp, - color: Colors.black - ), - ), + child: const CircularProgressIndicator(), + // Text( + // '视频加载中....', + // style: TextStyle( + // fontSize: 20.sp, + // color: Colors.black + // ), + // ), ), ); } diff --git a/lib/pages/section/bloc/section_bloc.dart b/lib/pages/section/bloc/section_bloc.dart index 13bdab5..05f690b 100644 --- a/lib/pages/section/bloc/section_bloc.dart +++ b/lib/pages/section/bloc/section_bloc.dart @@ -34,34 +34,31 @@ class SectionBloc extends Bloc { CourseUnitEntity get courseUnitEntity => _courseUnitEntity; - CourseUnitDetail _courseUnitDetail; + ///courseUnitId与课程环节列表的映射 + final Map?> _courseSectionDatasMap = {}; - CourseUnitDetail get courseUnitDetail => _courseUnitDetail; - - List? _courseSectionDatas; - - List? get courseSectionDatas => _courseSectionDatas; + Map?> get courseSectionDatasMap => _courseSectionDatasMap; CourseProcessEntity? _processEntity; CourseProcessEntity? get processEntity => _processEntity; - SectionBloc(this._courseUnitEntity, this._courseUnitDetail, + SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, this._listController) : super(LessonInitial()) { - on(_requestData); + on(_requestSectionsData); on(_requestEndClass); on(_requestEnterClass); on(_requestVideoLesson); on(_pageControllerChange); } - void _requestData( + void _requestSectionsData( RequestDataEvent event, Emitter emitter) async { try { await loading(() async { - _courseSectionDatas = - await LessonDao.courseSection(courseUnitId: _courseUnitDetail.id!); + _courseSectionDatasMap[event.courseUnitId] = + await LessonDao.courseSection(courseUnitId: event.courseUnitId); emitter(LessonDataLoadState()); }); } catch (e) { @@ -102,18 +99,18 @@ class SectionBloc extends Bloc { void _requestEndClass( RequestEndClassEvent event, Emitter emitter) async { - if (event.isLastPage) { - await await ListenDao.endClass(event.courseLessonId, event.currentStep, + if (event.isCompleted) { + await await ListenDao.endClass(event.courseLessonId, + currentStep: event.currentStep, currentTime: event.currentTime); } else { - await await ListenDao.exitClass(event.courseLessonId, event.currentStep, + await await ListenDao.exitClass(event.courseLessonId, + currentStep: event.currentStep, currentTime: event.currentTime); } - debugPrint("WQF _requestEndClass autoNextSection=${event.autoNextSection}"); if (event.autoNextSection) { final nextCourseSection = - getNextCourseSectionBySort(int.parse(event.courseLessonId)); - debugPrint("WQF nextCourseSection = $nextCourseSection"); + getNextCourseSection(int.parse(event.courseLessonId)); ///进入课堂 add(RequestEnterClassEvent(nextCourseSection!.id.toString(), nextCourseSection.courseType)); @@ -126,30 +123,72 @@ class SectionBloc extends Bloc { emitter(CurrentPageIndexState()); } - // 未锁定的页数 + // 未锁定的页(单元)数 int unlockPageCount() { return _courseUnitEntity.courseUnitVOList ?.indexWhereOrNull((element) => element.lock == true) ?? 1; } - CourseSectionEntity? getNextCourseSectionBySort(int courseLessonId) { - final curCourseSectionEntity = _courseSectionDatas - ?.firstWhere((element) => element.id == courseLessonId); - final curSort = curCourseSectionEntity?.sortOrder ?? 0; - CourseSectionEntity? nextCourseSectionEntity = _courseSectionDatas - ?.firstWhere((element) => element.sortOrder == curSort + 1); + CourseUnitDetail getCourseUnitDetail({int? pageIndex}) { + return _courseUnitEntity.courseUnitVOList![pageIndex ?? _currentPage]; + } + + ///根据courseLessonId查找对应的courseSection + CourseSectionEntity? findCourseSectionById(int courseLessonId) { + for (var entry in courseSectionDatasMap.entries) { + var sectionList = entry.value; + if (sectionList != null) { + for (var section in sectionList) { + if (section.id == courseLessonId) { + return section; + } + } + } + } + return null; + } + + ///根据sortOrder查找对应的courseSection + CourseSectionEntity? findCourseSectionBySort(int sortOrder) { + for (var entry in courseSectionDatasMap.entries) { + var sectionList = entry.value; + if (sectionList != null) { + for (var section in sectionList) { + if (section.sortOrder == sortOrder) { + return section; + } + } + } + } + return null; + } + + ///根据courseLessonId查找对应的CourseUnitDetail + CourseUnitDetail? findCourseUnitDetailById(int courseLessonId) { + final curCourseSectionEntity = findCourseSectionById(courseLessonId); + if (curCourseSectionEntity != null) { + final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList?.firstWhere((element) => + element.id == curCourseSectionEntity.courseUnitId); + return curCourseUnitDetail; + } + return null; + } + + CourseSectionEntity? getNextCourseSection(int courseLessonId) { + final curCourseSectionEntity = findCourseSectionById(courseLessonId); + final curSectionSort = curCourseSectionEntity?.sortOrder ?? 0; + final nextCourseSectionEntity = findCourseSectionBySort(curSectionSort + 1); if (nextCourseSectionEntity != null) { return nextCourseSectionEntity; } else { ///跨unit选lesson - final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList - ?.firstWhere( - (element) => element.id == curCourseSectionEntity?.courseUnitId); + final curCourseUnitDetail = findCourseUnitDetailById(courseLessonId); if (curCourseUnitDetail != null) { final nextCourseUnitDetail = _courseUnitEntity.courseUnitVOList - ?.firstWhere((element) => element.sortOrder == 0); + ?.firstWhere((element) => element.sortOrder == (curCourseUnitDetail.sortOrder! + 1)); if (nextCourseUnitDetail != null) { + add(RequestDataEvent(nextCourseUnitDetail.id!)); ///pageView翻页了,可能需要预加载 todo return null; } else { diff --git a/lib/pages/section/bloc/section_event.dart b/lib/pages/section/bloc/section_event.dart index 1936ae1..e9fa3ad 100644 --- a/lib/pages/section/bloc/section_event.dart +++ b/lib/pages/section/bloc/section_event.dart @@ -3,7 +3,11 @@ part of 'section_bloc.dart'; @immutable abstract class SectionEvent {} -class RequestDataEvent extends SectionEvent {} +class RequestDataEvent extends SectionEvent { + final int courseUnitId; + + RequestDataEvent(this.courseUnitId); +} ///获取视频课程内容 class RequestVideoLessonEvent extends SectionEvent { @@ -24,17 +28,23 @@ class RequestEnterClassEvent extends SectionEvent { ///结束课堂 class RequestEndClassEvent extends SectionEvent { final String courseLessonId; - final String currentStep; - ///是否是最后一页(决定调结束接口还是退出接口) - final bool isLastPage; + ///当前进展(进度类,比如练习、绘本) + final int? currentStep; + + ///当前时间(进度类,比如音视频) final int? currentTime; + ///课程环节是否完成(决定调结束接口还是退出接口) + final bool isCompleted; + ///自动进入下一环节 final bool autoNextSection; - RequestEndClassEvent(this.courseLessonId, this.currentStep, this.isLastPage, - {this.currentTime, this.autoNextSection = false}); + RequestEndClassEvent(this.courseLessonId, isCompleted, + {this.currentStep, this.currentTime, autoNextSection}) + : isCompleted = isCompleted ?? false, + autoNextSection = autoNextSection ?? false; } ///页面切换 diff --git a/lib/pages/section/section_page.dart b/lib/pages/section/section_page.dart index 3c64fe8..78fc39f 100644 --- a/lib/pages/section/section_page.dart +++ b/lib/pages/section/section_page.dart @@ -20,21 +20,21 @@ import 'courese_module_model.dart'; /// 环节列表页 class SectionPage extends StatelessWidget { const SectionPage( - {super.key, - required this.courseUnitEntity, - required this.courseUnitDetail}); + {super.key, required this.courseUnitEntity, required this.courseUnitId}); final CourseUnitEntity courseUnitEntity; /// unitId - final CourseUnitDetail courseUnitDetail; + final int courseUnitId; @override Widget build(BuildContext context) { + int initialPage = courseUnitEntity.courseUnitVOList + ?.indexWhere((element) => element.id == courseUnitId) ?? + 0; return BlocProvider( - create: (context) => SectionBloc(courseUnitEntity, courseUnitDetail, - PageController(), ScrollController()) - ..add(RequestDataEvent()), + create: (context) => SectionBloc(courseUnitEntity, initialPage, + PageController(initialPage: initialPage), ScrollController()), child: _SectionPageView(context), ); } @@ -76,16 +76,18 @@ class _SectionPageView extends StatelessWidget { }).then((value) { if (value != null) { Map dataMap = value as Map; - bloc.add(RequestEndClassEvent(dataMap['courseLessonId']!, - dataMap['currentTime']!, true, autoNextSection: dataMap['nextSection'] as bool)); + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, dataMap['isCompleted'], + currentTime: dataMap['currentTime'], + autoNextSection: dataMap['nextSection'])); } }); return; } if (state is RequestEnterClassState) { - if (state.courseType != SectionType.practice.value - && state.courseType != SectionType.pictureBook.value) { + if (state.courseType != SectionType.practice.value && + state.courseType != SectionType.pictureBook.value) { ///视频类型 ///获取视频课程内容 bloc.add(RequestVideoLessonEvent( @@ -101,8 +103,11 @@ class _SectionPageView extends StatelessWidget { if (value != null) { Map dataMap = value as Map; bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, dataMap['currentStep']!, - dataMap['isLastPage']! as bool, autoNextSection: dataMap['nextSection'] as bool)); + dataMap['courseLessonId']!, + dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'], + )); } }); return; @@ -116,8 +121,9 @@ class _SectionPageView extends StatelessWidget { if (value != null) { Map dataMap = value as Map; bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, dataMap['currentStep']!, - dataMap['isLastPage']! as bool, autoNextSection: dataMap['nextSection'] as bool)); + dataMap['courseLessonId']!, dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'])); } }); return; @@ -139,7 +145,7 @@ class _SectionPageView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SectionHeaderWidget( - title: bloc.courseUnitDetail.name, + title: bloc.getCourseUnitDetail().name, courseModuleCode: bloc.courseUnitEntity.courseModuleCode), Expanded( child: Padding( @@ -156,7 +162,10 @@ class _SectionPageView extends StatelessWidget { ///去掉 Android 上默认的边缘拖拽效果 behavior: ScrollConfiguration.of(context) .copyWith(overscroll: false), - child: _itemTransCard(index, context), + child: _itemTransCard( + bloc.getCourseUnitDetail(pageIndex: index), + index, + context), ); }), ), // 设置外部padding, @@ -209,55 +218,80 @@ class _SectionPageView extends StatelessWidget { }); } -Widget _itemTransCard(int index, BuildContext context) { +Widget _itemTransCard( + CourseUnitDetail courseUnitDetail, int pageIndex, BuildContext context) { final bloc = BlocProvider.of(context); - return NestedListView.builder( - itemCount: bloc.courseSectionDatas?.length ?? 0, - scrollDirection: Axis.horizontal, - itemBuilder: (BuildContext context, int index) { - CourseSectionEntity sectionData = bloc.courseSectionDatas![index]; - if (sectionData.courseType == SectionType.bouns.value) { - //彩蛋 - return GestureDetector( - onTap: () { - if (!UserUtil.isLogined()) { - pushNamed(AppRouteName.login); - return; - } - if (sectionData.lock == true) { - showToast('当前课程暂未解锁'); - return; - } + List? courseSectionEntities = + bloc.courseSectionDatasMap[courseUnitDetail.id]; + if (courseSectionEntities == null) { + bloc.add(RequestDataEvent(courseUnitDetail.id!)); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + '暂无数据', + style: TextStyle(fontSize: 24.0), + ), + const SizedBox(height: 16.0), + // 间距 + CircularProgressIndicator( + color: CourseModuleModel( + bloc.courseUnitEntity.courseModuleCode ?? 'Phase-1') + .color), + // 加载动画 + ], + ), + ); + } else { + return NestedListView.builder( + itemCount: bloc.courseSectionDatasMap[courseUnitDetail.id]?.length ?? 0, + scrollDirection: Axis.horizontal, + itemBuilder: (BuildContext context, int index) { + CourseSectionEntity sectionData = courseSectionEntities[index]; + if (sectionData.courseType == SectionType.bouns.value) { + //彩蛋 + return GestureDetector( + onTap: () { + if (!UserUtil.isLogined()) { + pushNamed(AppRouteName.login); + return; + } + if (sectionData.lock == true) { + showToast('当前课程暂未解锁'); + return; + } - ///进入课堂 - bloc.add(RequestEnterClassEvent( - sectionData.id.toString(), sectionData.courseType)); - }, - child: SectionBoundsItem( - imageUrl: sectionData.coverUrl, - ), - ); - } else { - return GestureDetector( - onTap: () { - if (!UserUtil.isLogined()) { - pushNamed(AppRouteName.login); - return; - } - if (sectionData.lock == true) { - showToast('当前课程暂未解锁'); - return; - } + ///进入课堂 + bloc.add(RequestEnterClassEvent( + sectionData.id.toString(), sectionData.courseType)); + }, + child: SectionBoundsItem( + imageUrl: sectionData.coverUrl, + ), + ); + } else { + return GestureDetector( + onTap: () { + if (!UserUtil.isLogined()) { + pushNamed(AppRouteName.login); + return; + } + if (sectionData.lock == true) { + showToast('当前课程暂未解锁'); + return; + } - ///进入课堂 - bloc.add(RequestEnterClassEvent( - sectionData.id.toString(), sectionData.courseType)); - }, - child: SectionVideoItem( - unitEntity: bloc.courseUnitEntity, - lessons: sectionData, - ), - ); - } - }); + ///进入课堂 + bloc.add(RequestEnterClassEvent( + sectionData.id.toString(), sectionData.courseType)); + }, + child: SectionVideoItem( + unitEntity: bloc.courseUnitEntity, + lessons: sectionData, + ), + ); + } + }); + } } diff --git a/lib/pages/unit/view.dart b/lib/pages/unit/view.dart index f61d56b..fc6971c 100644 --- a/lib/pages/unit/view.dart +++ b/lib/pages/unit/view.dart @@ -64,7 +64,7 @@ class UnitPage extends StatelessWidget { pushNamed(AppRouteName.courseSection, arguments: { 'courseUnitEntity': bloc.unitData, - 'courseUnitDetail': data + 'courseUnitId': data.id }); }, child: CourseUnitItem( diff --git a/lib/pages/video/lookvideo/widgets/video_widget.dart b/lib/pages/video/lookvideo/widgets/video_widget.dart index b81567c..2a88449 100644 --- a/lib/pages/video/lookvideo/widgets/video_widget.dart +++ b/lib/pages/video/lookvideo/widgets/video_widget.dart @@ -51,10 +51,7 @@ class _VideoWidgetState extends State { if (_controller!.value.isInitialized) { if (_controller!.value.isPlaying) { setState(() { - double currentSecond = - (_controller!.value.position.inMinutes.remainder(60) * 60 + - _controller!.value.position.inSeconds.remainder(60)) - .toDouble(); + double currentSecond = getCurrentPositionSeconds().toDouble(); int totalSecond = _controller!.value.duration.inMinutes.remainder(60) * 60 + _controller!.value.duration.inSeconds.remainder(60); @@ -75,13 +72,10 @@ class _VideoWidgetState extends State { _controller!.value.duration.inSeconds) { final lookVideoBloc = context.read(); lookVideoBloc.sectionComplete(() { - String currentTime = - (_controller!.value.position.inMinutes.remainder(60) * 60 + - _controller!.value.position.inSeconds.remainder(60)) - .toString(); popPage(data: { 'courseLessonId': widget.courseLessonId, - 'currentTime': currentTime, + 'currentTime': getCurrentPositionSeconds(), + 'isCompleted': true, 'nextSection': widget.isTopic }); } as VoidCallback, @@ -127,13 +121,9 @@ class _VideoWidgetState extends State { popPage(); return; } - String currentTime = - (_controller!.value.position.inMinutes.remainder(60) * 60 + - _controller!.value.position.inSeconds.remainder(60)) - .toString(); popPage(data: { 'courseLessonId': widget.courseLessonId, - 'currentTime': currentTime + 'currentTime': getCurrentPositionSeconds(), }); } } else if (type == OperationType.playState) { @@ -250,10 +240,11 @@ class _VideoWidgetState extends State { ) : Container( color: Colors.white, - child: Text( - '视频加载中....', - style: TextStyle(fontSize: 20.sp, color: Colors.black), - ), + child: const CircularProgressIndicator(), + // Text( + // '视频加载中....', + // style: TextStyle(fontSize: 20.sp, color: Colors.black), + // ), ), ), ), @@ -270,4 +261,10 @@ class _VideoWidgetState extends State { } super.dispose(); } + + ///获取当前进度秒数 + int getCurrentPositionSeconds() { + return (_controller!.value.position.inMinutes.remainder(60) * 60 + + _controller!.value.position.inSeconds.remainder(60)); + } } diff --git a/lib/route/route.dart b/lib/route/route.dart index d1c3343..eecdf7f 100644 --- a/lib/route/route.dart +++ b/lib/route/route.dart @@ -126,17 +126,17 @@ class AppRouter { builder: (_) => UnitPage(courseModuleEntity: courseModuleEntity)); case AppRouteName.courseSection: CourseUnitEntity courseUnitEntity = CourseUnitEntity(); - CourseUnitDetail courseUnitDetail = CourseUnitDetail(); + int courseUnitDetail = 0; if (settings.arguments != null) { courseUnitEntity = (settings.arguments as Map) .getOrNull('courseUnitEntity') as CourseUnitEntity; courseUnitDetail = (settings.arguments as Map) - .getOrNull('courseUnitDetail') as CourseUnitDetail; + .getOrNull('courseUnitId'); } return CupertinoPageRoute( builder: (_) => SectionPage( courseUnitEntity: courseUnitEntity, - courseUnitDetail: courseUnitDetail)); + courseUnitId: courseUnitDetail)); case AppRouteName.listen: return CupertinoPageRoute(builder: (_) => const ListenPage()); case AppRouteName.shop: