Commit 3ba925a9d0b3386434fe89c90552b3482a056dd5

Authored by 吴启风
1 parent 27dad3d1

feat:环节页增加翻页切换单元效果

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&lt;SectionEvent, SectionState&gt; { @@ -32,11 +46,12 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
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&lt;SectionEvent, SectionState&gt; { @@ -82,4 +97,15 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
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 &#39;courese_module_model.dart&#39;; @@ -16,7 +18,10 @@ import &#39;courese_module_model.dart&#39;;
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
  1 +
  2 +
  3 +extension ListExtension<E> on List<E> {
  4 + /// 获取数组中第一个匹配元素的index,没有就返回null
  5 + int? indexWhereOrNull(bool Function(E element) test) {
  6 + for (int i = 0; i < length; i++) {
  7 + if (test(this[i])) return i;
  8 + }
  9 + return null;
  10 + }
  11 +}
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