Commit 3ba925a9d0b3386434fe89c90552b3482a056dd5
1 parent
27dad3d1
feat:环节页增加翻页切换单元效果
Showing
6 changed files
with
176 additions
and
98 deletions
lib/pages/section/bloc/section_bloc.dart
| @@ -10,12 +10,26 @@ import 'package:wow_english/utils/toast_util.dart'; | @@ -10,12 +10,26 @@ import 'package:wow_english/utils/toast_util.dart'; | ||
| 10 | 10 | ||
| 11 | import '../../../models/course_section_entity.dart'; | 11 | import '../../../models/course_section_entity.dart'; |
| 12 | import '../../../models/course_unit_entity.dart'; | 12 | import '../../../models/course_unit_entity.dart'; |
| 13 | +import '../../../utils/list_ext.dart'; | ||
| 13 | 14 | ||
| 14 | part 'section_event.dart'; | 15 | part 'section_event.dart'; |
| 15 | part 'section_state.dart'; | 16 | part 'section_state.dart'; |
| 16 | 17 | ||
| 17 | class SectionBloc extends Bloc<SectionEvent, SectionState> { | 18 | class SectionBloc extends Bloc<SectionEvent, SectionState> { |
| 18 | 19 | ||
| 20 | + PageController _pageController; | ||
| 21 | + | ||
| 22 | + PageController get pageController => _pageController; | ||
| 23 | + | ||
| 24 | + ///当前页索引 | ||
| 25 | + int _currentPage = 0; | ||
| 26 | + | ||
| 27 | + int get currentPage => _currentPage; | ||
| 28 | + | ||
| 29 | + ScrollController _listController; | ||
| 30 | + | ||
| 31 | + ScrollController get listController => _listController; | ||
| 32 | + | ||
| 19 | CourseUnitEntity _courseUnitEntity; | 33 | CourseUnitEntity _courseUnitEntity; |
| 20 | 34 | ||
| 21 | CourseUnitEntity get courseUnitEntity => _courseUnitEntity; | 35 | CourseUnitEntity get courseUnitEntity => _courseUnitEntity; |
| @@ -32,11 +46,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -32,11 +46,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
| 32 | 46 | ||
| 33 | CourseProcessEntity? get processEntity => _processEntity; | 47 | CourseProcessEntity? get processEntity => _processEntity; |
| 34 | 48 | ||
| 35 | - SectionBloc(this._courseUnitEntity, this._courseUnitDetail) : super(LessonInitial()) { | 49 | + SectionBloc(this._courseUnitEntity, this._courseUnitDetail, this._pageController, this._listController) : super(LessonInitial()) { |
| 36 | on<RequestDataEvent>(_requestData); | 50 | on<RequestDataEvent>(_requestData); |
| 37 | on<RequestExitClassEvent>(_requestExitClass); | 51 | on<RequestExitClassEvent>(_requestExitClass); |
| 38 | on<RequestEnterClassEvent>(_requestEnterClass); | 52 | on<RequestEnterClassEvent>(_requestEnterClass); |
| 39 | on<RequestVideoLessonEvent>(_requestVideoLesson); | 53 | on<RequestVideoLessonEvent>(_requestVideoLesson); |
| 54 | + on<CurrentUnitIndexChangeEvent>(_pageControllerChange); | ||
| 40 | } | 55 | } |
| 41 | 56 | ||
| 42 | void _requestData(RequestDataEvent event, Emitter<SectionState> emitter) async { | 57 | void _requestData(RequestDataEvent event, Emitter<SectionState> emitter) async { |
| @@ -82,4 +97,15 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -82,4 +97,15 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
| 82 | void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { | 97 | void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { |
| 83 | await ListenDao.exitClass(event.courseLessonId,event.currentStep,event.currentTime); | 98 | await ListenDao.exitClass(event.courseLessonId,event.currentStep,event.currentTime); |
| 84 | } | 99 | } |
| 100 | + | ||
| 101 | + void _pageControllerChange(CurrentUnitIndexChangeEvent event, | ||
| 102 | + Emitter<SectionState> emitter) async { | ||
| 103 | + _currentPage = event.unitIndex; | ||
| 104 | + emitter(CurrentPageIndexState()); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + // 未锁定的页数 | ||
| 108 | + int unlockPageCount() { | ||
| 109 | + return _courseUnitEntity.courseUnitVOList?.indexWhereOrNull((element) => element.lock == true) ?? 1; | ||
| 110 | + } | ||
| 85 | } | 111 | } |
lib/pages/section/bloc/section_event.dart
| @@ -26,3 +26,9 @@ class RequestExitClassEvent extends SectionEvent { | @@ -26,3 +26,9 @@ class RequestExitClassEvent extends SectionEvent { | ||
| 26 | final String currentTime; | 26 | final String currentTime; |
| 27 | RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); | 27 | RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); |
| 28 | } | 28 | } |
| 29 | + | ||
| 30 | +///页面切换 | ||
| 31 | +class CurrentUnitIndexChangeEvent extends SectionEvent { | ||
| 32 | + final int unitIndex; | ||
| 33 | + CurrentUnitIndexChangeEvent(this.unitIndex); | ||
| 34 | +} |
lib/pages/section/bloc/section_state.dart
| @@ -10,11 +10,15 @@ class LessonDataLoadState extends SectionState {} | @@ -10,11 +10,15 @@ class LessonDataLoadState extends SectionState {} | ||
| 10 | class RequestVideoLessonState extends SectionState { | 10 | class RequestVideoLessonState extends SectionState { |
| 11 | final String courseLessonId; | 11 | final String courseLessonId; |
| 12 | final int type; | 12 | final int type; |
| 13 | - RequestVideoLessonState(this.courseLessonId,this.type); | 13 | + |
| 14 | + RequestVideoLessonState(this.courseLessonId, this.type); | ||
| 14 | } | 15 | } |
| 15 | 16 | ||
| 16 | -class RequestEnterClassState extends SectionState{ | 17 | +class RequestEnterClassState extends SectionState { |
| 17 | final String courseLessonId; | 18 | final String courseLessonId; |
| 18 | final int courseType; | 19 | final int courseType; |
| 19 | - RequestEnterClassState(this.courseLessonId,this.courseType); | 20 | + |
| 21 | + RequestEnterClassState(this.courseLessonId, this.courseType); | ||
| 20 | } | 22 | } |
| 23 | + | ||
| 24 | +class CurrentPageIndexState extends SectionState {} |
lib/pages/section/section_page.dart
| 1 | +import 'package:flutter/cupertino.dart'; | ||
| 1 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
| 2 | import 'package:flutter_bloc/flutter_bloc.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'; |
| 5 | +import 'package:nested_scroll_views/material.dart'; | ||
| 4 | import 'package:wow_english/common/core/user_util.dart'; | 6 | import 'package:wow_english/common/core/user_util.dart'; |
| 5 | import 'package:wow_english/common/extension/string_extension.dart'; | 7 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 6 | import 'package:wow_english/models/course_unit_entity.dart'; | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
| @@ -16,7 +18,10 @@ import 'courese_module_model.dart'; | @@ -16,7 +18,10 @@ import 'courese_module_model.dart'; | ||
| 16 | 18 | ||
| 17 | /// 环节列表页 | 19 | /// 环节列表页 |
| 18 | class SectionPage extends StatelessWidget { | 20 | class SectionPage extends StatelessWidget { |
| 19 | - const SectionPage({super.key, required this.courseUnitEntity, required this.courseUnitDetail}); | 21 | + const SectionPage( |
| 22 | + {super.key, | ||
| 23 | + required this.courseUnitEntity, | ||
| 24 | + required this.courseUnitDetail}); | ||
| 20 | 25 | ||
| 21 | final CourseUnitEntity courseUnitEntity; | 26 | final CourseUnitEntity courseUnitEntity; |
| 22 | 27 | ||
| @@ -26,7 +31,9 @@ class SectionPage extends StatelessWidget { | @@ -26,7 +31,9 @@ class SectionPage extends StatelessWidget { | ||
| 26 | @override | 31 | @override |
| 27 | Widget build(BuildContext context) { | 32 | Widget build(BuildContext context) { |
| 28 | return BlocProvider( | 33 | return BlocProvider( |
| 29 | - create: (context) => SectionBloc(courseUnitEntity, courseUnitDetail)..add(RequestDataEvent()), | 34 | + create: (context) => SectionBloc(courseUnitEntity, courseUnitDetail, |
| 35 | + PageController(), ScrollController()) | ||
| 36 | + ..add(RequestDataEvent()), | ||
| 30 | child: _SectionPageView(context), | 37 | child: _SectionPageView(context), |
| 31 | ); | 38 | ); |
| 32 | } | 39 | } |
| @@ -115,11 +122,11 @@ class _SectionPageView extends StatelessWidget { | @@ -115,11 +122,11 @@ class _SectionPageView extends StatelessWidget { | ||
| 115 | } | 122 | } |
| 116 | } | 123 | } |
| 117 | }, | 124 | }, |
| 118 | - child: _homeView(), | 125 | + child: _sectionView(), |
| 119 | ); | 126 | ); |
| 120 | } | 127 | } |
| 121 | 128 | ||
| 122 | - Widget _homeView() => | 129 | + Widget _sectionView() => |
| 123 | BlocBuilder<SectionBloc, SectionState>(builder: (context, state) { | 130 | BlocBuilder<SectionBloc, SectionState>(builder: (context, state) { |
| 124 | final bloc = BlocProvider.of<SectionBloc>(context); | 131 | final bloc = BlocProvider.of<SectionBloc>(context); |
| 125 | return Scaffold( | 132 | return Scaffold( |
| @@ -133,96 +140,65 @@ class _SectionPageView extends StatelessWidget { | @@ -133,96 +140,65 @@ class _SectionPageView extends StatelessWidget { | ||
| 133 | title: bloc.courseUnitDetail.name, | 140 | title: bloc.courseUnitDetail.name, |
| 134 | courseModuleCode: bloc.courseUnitEntity.courseModuleCode), | 141 | courseModuleCode: bloc.courseUnitEntity.courseModuleCode), |
| 135 | Expanded( | 142 | Expanded( |
| 136 | - child: ListView.builder( | ||
| 137 | - itemCount: bloc.courseSectionDatas?.length ?? 0, | ||
| 138 | - scrollDirection: Axis.horizontal, | ||
| 139 | - itemBuilder: (BuildContext context, int index) { | ||
| 140 | - CourseSectionEntity sectionData = | ||
| 141 | - bloc.courseSectionDatas![index]; | ||
| 142 | - if (sectionData.courseType == 5) { | ||
| 143 | - //彩蛋 | ||
| 144 | - return GestureDetector( | ||
| 145 | - onTap: () { | ||
| 146 | - if (!UserUtil.isLogined()) { | ||
| 147 | - pushNamed(AppRouteName.login); | ||
| 148 | - return; | ||
| 149 | - } | ||
| 150 | - if (sectionData.lock == true) { | ||
| 151 | - showToast('当前课程暂未解锁'); | ||
| 152 | - return; | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - ///进入课堂 | ||
| 156 | - bloc.add(RequestEnterClassEvent( | ||
| 157 | - sectionData.id.toString(), sectionData.courseType)); | ||
| 158 | - }, | ||
| 159 | - child: SectionBoundsItem( | ||
| 160 | - imageUrl: sectionData.coverUrl, | ||
| 161 | - ), | ||
| 162 | - ); | ||
| 163 | - } else { | ||
| 164 | - return GestureDetector( | ||
| 165 | - onTap: () { | ||
| 166 | - if (!UserUtil.isLogined()) { | ||
| 167 | - pushNamed(AppRouteName.login); | ||
| 168 | - return; | ||
| 169 | - } | ||
| 170 | - if (sectionData.lock == true) { | ||
| 171 | - showToast('当前课程暂未解锁'); | ||
| 172 | - return; | ||
| 173 | - } | ||
| 174 | - | ||
| 175 | - ///进入课堂 | ||
| 176 | - bloc.add(RequestEnterClassEvent( | ||
| 177 | - sectionData.id.toString(), sectionData.courseType)); | ||
| 178 | - }, | ||
| 179 | - child: SectionVideoItem( | ||
| 180 | - unitEntity: bloc.courseUnitEntity, | ||
| 181 | - lessons: sectionData, | ||
| 182 | - ), | ||
| 183 | - ); | ||
| 184 | - } | ||
| 185 | - })), | ||
| 186 | - // SafeArea( | ||
| 187 | - // child: Padding( | ||
| 188 | - // padding: EdgeInsets.symmetric(horizontal: 13.w), | ||
| 189 | - // child: Row( | ||
| 190 | - // mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
| 191 | - // children: [ | ||
| 192 | - // SizedBox( | ||
| 193 | - // height: 47.h, | ||
| 194 | - // width: 80.w, | ||
| 195 | - // ), | ||
| 196 | - // Container( | ||
| 197 | - // decoration: BoxDecoration( | ||
| 198 | - // color: CourseModuleModel( | ||
| 199 | - // bloc.courseUnitEntity.courseModuleCode ?? | ||
| 200 | - // 'Phase-1') | ||
| 201 | - // .color, | ||
| 202 | - // borderRadius: BorderRadius.circular(14.5.r), | ||
| 203 | - // ), | ||
| 204 | - // padding: EdgeInsets.symmetric( | ||
| 205 | - // vertical: 8.h, horizontal: 24.w), | ||
| 206 | - // child: Text( | ||
| 207 | - // '${(bloc.courseUnitEntity.nowStep ?? 0)}/${bloc.courseUnitEntity.total ?? 0}', | ||
| 208 | - // style: TextStyle( | ||
| 209 | - // color: Colors.white, fontSize: 12.sp), | ||
| 210 | - // ), | ||
| 211 | - // ), | ||
| 212 | - // Image.asset( | ||
| 213 | - // CourseModuleModel( | ||
| 214 | - // bloc.courseUnitEntity.courseModuleCode ?? | ||
| 215 | - // 'Phase-1') | ||
| 216 | - // .courseModuleLogo | ||
| 217 | - // .assetPng, | ||
| 218 | - // height: 47.h, | ||
| 219 | - // width: 80.w, | ||
| 220 | - // // color: Colors.red, | ||
| 221 | - // ), | ||
| 222 | - // ], | ||
| 223 | - // ), | ||
| 224 | - // ), | ||
| 225 | - // ) | 143 | + child: Padding( |
| 144 | + padding: EdgeInsets.symmetric(horizontal: 10.w), | ||
| 145 | + child: OverflowBox( | ||
| 146 | + child: NestedPageView.builder( | ||
| 147 | + itemCount: bloc.unlockPageCount(), | ||
| 148 | + controller: bloc.pageController, | ||
| 149 | + onPageChanged: (int index) { | ||
| 150 | + bloc.add(CurrentUnitIndexChangeEvent(index)); | ||
| 151 | + }, | ||
| 152 | + itemBuilder: (context, index) { | ||
| 153 | + return ScrollConfiguration( | ||
| 154 | + ///去掉 Android 上默认的边缘拖拽效果 | ||
| 155 | + behavior: ScrollConfiguration.of(context) | ||
| 156 | + .copyWith(overscroll: false), | ||
| 157 | + child: _itemTransCard(index, context), | ||
| 158 | + ); | ||
| 159 | + }), | ||
| 160 | + ), // 设置外部padding, | ||
| 161 | + )), | ||
| 162 | + SafeArea( | ||
| 163 | + child: Padding( | ||
| 164 | + padding: EdgeInsets.symmetric(horizontal: 13.w), | ||
| 165 | + child: Row( | ||
| 166 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
| 167 | + children: [ | ||
| 168 | + SizedBox( | ||
| 169 | + height: 47.h, | ||
| 170 | + width: 80.w, | ||
| 171 | + ), | ||
| 172 | + Container( | ||
| 173 | + decoration: BoxDecoration( | ||
| 174 | + color: CourseModuleModel( | ||
| 175 | + bloc.courseUnitEntity.courseModuleCode ?? | ||
| 176 | + 'Phase-1') | ||
| 177 | + .color, | ||
| 178 | + borderRadius: BorderRadius.circular(14.5.r), | ||
| 179 | + ), | ||
| 180 | + padding: EdgeInsets.symmetric( | ||
| 181 | + vertical: 8.h, horizontal: 24.w), | ||
| 182 | + child: Text( | ||
| 183 | + '${bloc.currentPage + 1}/${bloc.unlockPageCount()}', | ||
| 184 | + style: TextStyle( | ||
| 185 | + color: Colors.white, fontSize: 12.sp), | ||
| 186 | + ), | ||
| 187 | + ), | ||
| 188 | + Image.asset( | ||
| 189 | + CourseModuleModel( | ||
| 190 | + bloc.courseUnitEntity.courseModuleCode ?? | ||
| 191 | + 'Phase-1') | ||
| 192 | + .courseModuleLogo | ||
| 193 | + .assetPng, | ||
| 194 | + height: 47.h, | ||
| 195 | + width: 80.w, | ||
| 196 | + // color: Colors.red, | ||
| 197 | + ), | ||
| 198 | + ], | ||
| 199 | + ), | ||
| 200 | + ), | ||
| 201 | + ) | ||
| 226 | ], | 202 | ], |
| 227 | ), | 203 | ), |
| 228 | ), | 204 | ), |
| @@ -230,3 +206,56 @@ class _SectionPageView extends StatelessWidget { | @@ -230,3 +206,56 @@ class _SectionPageView extends StatelessWidget { | ||
| 230 | ); | 206 | ); |
| 231 | }); | 207 | }); |
| 232 | } | 208 | } |
| 209 | + | ||
| 210 | +Widget _itemTransCard(int index, BuildContext context) { | ||
| 211 | + final bloc = BlocProvider.of<SectionBloc>(context); | ||
| 212 | + return NestedListView.builder( | ||
| 213 | + itemCount: bloc.courseSectionDatas?.length ?? 0, | ||
| 214 | + scrollDirection: Axis.horizontal, | ||
| 215 | + itemBuilder: (BuildContext context, int index) { | ||
| 216 | + CourseSectionEntity sectionData = bloc.courseSectionDatas![index]; | ||
| 217 | + if (sectionData.courseType == 5) { | ||
| 218 | + //彩蛋 | ||
| 219 | + return GestureDetector( | ||
| 220 | + onTap: () { | ||
| 221 | + if (!UserUtil.isLogined()) { | ||
| 222 | + pushNamed(AppRouteName.login); | ||
| 223 | + return; | ||
| 224 | + } | ||
| 225 | + if (sectionData.lock == true) { | ||
| 226 | + showToast('当前课程暂未解锁'); | ||
| 227 | + return; | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + ///进入课堂 | ||
| 231 | + bloc.add(RequestEnterClassEvent( | ||
| 232 | + sectionData.id.toString(), sectionData.courseType)); | ||
| 233 | + }, | ||
| 234 | + child: SectionBoundsItem( | ||
| 235 | + imageUrl: sectionData.coverUrl, | ||
| 236 | + ), | ||
| 237 | + ); | ||
| 238 | + } else { | ||
| 239 | + return GestureDetector( | ||
| 240 | + onTap: () { | ||
| 241 | + if (!UserUtil.isLogined()) { | ||
| 242 | + pushNamed(AppRouteName.login); | ||
| 243 | + return; | ||
| 244 | + } | ||
| 245 | + if (sectionData.lock == true) { | ||
| 246 | + showToast('当前课程暂未解锁'); | ||
| 247 | + return; | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + ///进入课堂 | ||
| 251 | + bloc.add(RequestEnterClassEvent( | ||
| 252 | + sectionData.id.toString(), sectionData.courseType)); | ||
| 253 | + }, | ||
| 254 | + child: SectionVideoItem( | ||
| 255 | + unitEntity: bloc.courseUnitEntity, | ||
| 256 | + lessons: sectionData, | ||
| 257 | + ), | ||
| 258 | + ); | ||
| 259 | + } | ||
| 260 | + }); | ||
| 261 | +} |
lib/utils/list_ext.dart
0 → 100644
pubspec.yaml
| @@ -111,6 +111,8 @@ dependencies: | @@ -111,6 +111,8 @@ dependencies: | ||
| 111 | umeng_common_sdk: ^1.2.7 | 111 | umeng_common_sdk: ^1.2.7 |
| 112 | # 友盟APM https://pub-web.flutter-io.cn/packages/umeng_apm_sdk | 112 | # 友盟APM https://pub-web.flutter-io.cn/packages/umeng_apm_sdk |
| 113 | umeng_apm_sdk: ^2.2.1 | 113 | umeng_apm_sdk: ^2.2.1 |
| 114 | + # 嵌套滚动 https://pub.dev/packages/nested_scroll_views | ||
| 115 | + nested_scroll_views: ^0.0.10 | ||
| 114 | 116 | ||
| 115 | dev_dependencies: | 117 | dev_dependencies: |
| 116 | build_runner: ^2.4.4 | 118 | build_runner: ^2.4.4 |