Commit 2b3842c55ad6f089f824572e5c916195bc07e4a0

Authored by xiaoyu
2 parents 93825fa5 9e085b2a

Merge branch 'feat-wqf-payment' into ios_umeng

.gitignore
@@ -68,6 +68,7 @@ @@ -68,6 +68,7 @@
68 **/ios/xcode_build_ipa_adh 68 **/ios/xcode_build_ipa_adh
69 **/ios/xcode_build_ipa_aps 69 **/ios/xcode_build_ipa_aps
70 **/ios/xcode_build_ipa_dev 70 **/ios/xcode_build_ipa_dev
  71 +**/ios/build/
71 72
72 # Exceptions to above rules. 73 # Exceptions to above rules.
73 !**/ios/**/default.mode1v3 74 !**/ios/**/default.mode1v3
lib/pages/home/bloc.dart
@@ -16,7 +16,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> { @@ -16,7 +16,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
16 16
17 void _init(InitEvent event, Emitter<HomeState> emit) async { 17 void _init(InitEvent event, Emitter<HomeState> emit) async {
18 await _checkUpdate(emit); 18 await _checkUpdate(emit);
19 - debugPrint('WQF ModuleSelectBloc _init');  
20 } 19 }
21 20
22 Future<void> _checkUpdate(Emitter<HomeState> emit) async { 21 Future<void> _checkUpdate(Emitter<HomeState> emit) async {
lib/pages/home/view.dart
@@ -13,7 +13,6 @@ import &#39;package:wow_english/pages/user/bloc/user_bloc.dart&#39;; @@ -13,7 +13,6 @@ import &#39;package:wow_english/pages/user/bloc/user_bloc.dart&#39;;
13 13
14 import '../../common/core/user_util.dart'; 14 import '../../common/core/user_util.dart';
15 import '../../common/dialogs/show_dialog.dart'; 15 import '../../common/dialogs/show_dialog.dart';
16 -import '../../utils/log_util.dart';  
17 import 'bloc.dart'; 16 import 'bloc.dart';
18 import 'event.dart'; 17 import 'event.dart';
19 import 'package:flutter_screenutil/flutter_screenutil.dart'; 18 import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -38,11 +37,9 @@ class _HomePageView extends StatelessWidget { @@ -38,11 +37,9 @@ class _HomePageView extends StatelessWidget {
38 Widget build(BuildContext context) { 37 Widget build(BuildContext context) {
39 return MultiBlocListener(listeners: [ 38 return MultiBlocListener(listeners: [
40 BlocListener<UserBloc, UserState>(listener: (context, state) { 39 BlocListener<UserBloc, UserState>(listener: (context, state) {
41 - debugPrint('WQF ModuleSelectPage BlocListener state: $state');  
42 }), 40 }),
43 BlocListener<HomeBloc, HomeState>( 41 BlocListener<HomeBloc, HomeState>(
44 listener: (context, state) { 42 listener: (context, state) {
45 - Log.d("WQF HomePage listener state: $state");  
46 if (state is UpdateDialogState) { 43 if (state is UpdateDialogState) {
47 _showUpdateDialog(context, state.forceUpdate, state.appVersionEntity); 44 _showUpdateDialog(context, state.forceUpdate, state.appVersionEntity);
48 } 45 }
@@ -97,8 +94,6 @@ class _HomePageView extends StatelessWidget { @@ -97,8 +94,6 @@ class _HomePageView extends StatelessWidget {
97 Expanded( 94 Expanded(
98 child: BlocBuilder<UserBloc, UserState>( 95 child: BlocBuilder<UserBloc, UserState>(
99 builder: (context, userState) { 96 builder: (context, userState) {
100 - debugPrint(  
101 - 'WQF ModuleSelectPage BlocBuilder state: $userState');  
102 return GestureDetector( 97 return GestureDetector(
103 onTap: () { 98 onTap: () {
104 _checkPermission(() { 99 _checkPermission(() {
lib/pages/practice/bloc/topic_picture_bloc.dart
@@ -103,7 +103,7 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt @@ -103,7 +103,7 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt
103 } else { 103 } else {
104 // 答对后且播放完自动翻页 104 // 答对后且播放完自动翻页
105 pageController.nextPage( 105 pageController.nextPage(
106 - duration: const Duration(milliseconds: 500), 106 + duration: const Duration(milliseconds: 250),
107 curve: Curves.ease, 107 curve: Curves.ease,
108 ); 108 );
109 } 109 }
@@ -332,7 +332,6 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt @@ -332,7 +332,6 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt
332 'nextSection': true 332 'nextSection': true
333 }); 333 });
334 }, againSectionTap: () { 334 }, againSectionTap: () {
335 - debugPrint("WQF 重做");  
336 pageController.jumpToPage(0); 335 pageController.jumpToPage(0);
337 }); 336 });
338 } 337 }
lib/pages/reading/bloc/reading_bloc.dart
@@ -287,7 +287,7 @@ class ReadingPageBloc @@ -287,7 +287,7 @@ class ReadingPageBloc
287 } else { 287 } else {
288 _currentPage += 1; 288 _currentPage += 1;
289 pageController.nextPage( 289 pageController.nextPage(
290 - duration: const Duration(milliseconds: 500), 290 + duration: const Duration(milliseconds: 250),
291 curve: Curves.ease, 291 curve: Curves.ease,
292 ); 292 );
293 } 293 }
lib/pages/section/bloc/section_bloc.dart
1 import 'package:flutter/cupertino.dart'; 1 import 'package:flutter/cupertino.dart';
2 import 'package:flutter/foundation.dart'; 2 import 'package:flutter/foundation.dart';
  3 +import 'package:flutter/material.dart';
3 import 'package:flutter_bloc/flutter_bloc.dart'; 4 import 'package:flutter_bloc/flutter_bloc.dart';
  5 +import 'package:flutter_screenutil/flutter_screenutil.dart';
4 import 'package:wow_english/common/request/dao/lesson_dao.dart'; 6 import 'package:wow_english/common/request/dao/lesson_dao.dart';
5 import 'package:wow_english/common/request/exception.dart'; 7 import 'package:wow_english/common/request/exception.dart';
6 import 'package:wow_english/common/request/dao/listen_dao.dart'; 8 import 'package:wow_english/common/request/dao/listen_dao.dart';
@@ -13,10 +15,10 @@ import &#39;../../../models/course_unit_entity.dart&#39;; @@ -13,10 +15,10 @@ import &#39;../../../models/course_unit_entity.dart&#39;;
13 import '../../../utils/list_ext.dart'; 15 import '../../../utils/list_ext.dart';
14 16
15 part 'section_event.dart'; 17 part 'section_event.dart';
  18 +
16 part 'section_state.dart'; 19 part 'section_state.dart';
17 20
18 class SectionBloc extends Bloc<SectionEvent, SectionState> { 21 class SectionBloc extends Bloc<SectionEvent, SectionState> {
19 -  
20 PageController _pageController; 22 PageController _pageController;
21 23
22 PageController get pageController => _pageController; 24 PageController get pageController => _pageController;
@@ -30,10 +32,17 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -30,10 +32,17 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
30 32
31 ScrollController get listController => _listController; 33 ScrollController get listController => _listController;
32 34
  35 + ScrollController _indicatorSrollController;
  36 +
  37 + ScrollController get indicatorSrollController => _indicatorSrollController;
  38 +
33 CourseUnitEntity _courseUnitEntity; 39 CourseUnitEntity _courseUnitEntity;
34 40
35 CourseUnitEntity get courseUnitEntity => _courseUnitEntity; 41 CourseUnitEntity get courseUnitEntity => _courseUnitEntity;
36 42
  43 + ///单元列表是否有刷新,有的话返回上一页时通知其刷新接口数据
  44 + bool courseUnitEntityChanged = false;
  45 +
37 ///courseUnitId与课程环节列表的映射 46 ///courseUnitId与课程环节列表的映射
38 final Map<int, List<CourseSectionEntity>?> _courseSectionDatasMap = {}; 47 final Map<int, List<CourseSectionEntity>?> _courseSectionDatasMap = {};
39 48
@@ -45,7 +54,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -45,7 +54,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
45 CourseProcessEntity? get processEntity => _processEntity; 54 CourseProcessEntity? get processEntity => _processEntity;
46 55
47 SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, 56 SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController,
48 - this._listController) 57 + this._listController, this._indicatorSrollController)
49 : super(LessonInitial()) { 58 : super(LessonInitial()) {
50 on<RequestDataEvent>(_requestSectionsData); 59 on<RequestDataEvent>(_requestSectionsData);
51 on<RequestEndClassEvent>(_requestEndClass); 60 on<RequestEndClassEvent>(_requestEndClass);
@@ -121,15 +130,38 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -121,15 +130,38 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
121 void _pageControllerChange( 130 void _pageControllerChange(
122 CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async { 131 CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async {
123 _currentPage = event.unitIndex; 132 _currentPage = event.unitIndex;
  133 + double indicatorWidth = 30.0.w; // 指示器宽度
  134 +
  135 + // 计算选中项的偏移量
  136 + double offset = _currentPage * indicatorWidth -
  137 + (_indicatorSrollController.position.viewportDimension -
  138 + indicatorWidth) /
  139 + 2;
  140 +
  141 + // 确保偏移量在合理范围内
  142 + if (offset < 0) {
  143 + offset = 0;
  144 + } else if (offset >
  145 + unlockPageCount() * indicatorWidth -
  146 + _indicatorSrollController.position.viewportDimension) {
  147 + offset = unlockPageCount() * indicatorWidth -
  148 + _indicatorSrollController.position.viewportDimension;
  149 + }
  150 + _indicatorSrollController.animateTo(
  151 + offset,
  152 + duration: const Duration(milliseconds: 250),
  153 + curve: Curves.easeInOut,
  154 + );
124 emitter(CurrentPageIndexState()); 155 emitter(CurrentPageIndexState());
125 } 156 }
126 157
127 ///未锁定的页(单元)数 158 ///未锁定的页(单元)数
128 int unlockPageCount() { 159 int unlockPageCount() {
129 - return _courseUnitEntity.courseUnitVOList  
130 - ?.indexWhereOrNull((element) => element.lock == true) ??  
131 - _courseUnitEntity.courseUnitVOList?.length ??  
132 - 0; 160 + // return _courseUnitEntity.courseUnitVOList
  161 + // ?.indexWhereOrNull((element) => element.lock == true) ??
  162 + // _courseUnitEntity.courseUnitVOList?.length ??
  163 + // 0;
  164 + return _courseUnitEntity.courseUnitVOList?.length ?? 0;
133 } 165 }
134 166
135 ///当前页的课程详情 167 ///当前页的课程详情
@@ -172,7 +204,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -172,7 +204,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
172 final curCourseSectionEntity = findCourseSectionById(courseLessonId); 204 final curCourseSectionEntity = findCourseSectionById(courseLessonId);
173 if (curCourseSectionEntity != null) { 205 if (curCourseSectionEntity != null) {
174 final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList 206 final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList
175 - ?.firstWhere( 207 + ?.firstWhereOrNull(
176 (element) => element.id == curCourseSectionEntity.courseUnitId); 208 (element) => element.id == curCourseSectionEntity.courseUnitId);
177 return curCourseUnitDetail; 209 return curCourseUnitDetail;
178 } 210 }
@@ -185,39 +217,89 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -185,39 +217,89 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
185 final curCourseSectionEntity = findCourseSectionById(courseLessonId); 217 final curCourseSectionEntity = findCourseSectionById(courseLessonId);
186 final curSectionSort = curCourseSectionEntity?.sortOrder ?? 0; 218 final curSectionSort = curCourseSectionEntity?.sortOrder ?? 0;
187 219
188 - ///查找下一个section  
189 - final nextCourseSectionEntity = findCourseSectionBySort(curSectionSort + 1);  
190 - if (nextCourseSectionEntity != null) {  
191 - return nextCourseSectionEntity; 220 + try {
  221 + ///查找当前unit的下一个section
  222 + CourseSectionEntity? nextCourseSectionEntity =
  223 + findCourseSectionBySort(curSectionSort + 1);
  224 + return checkCourseSectionLocked(courseLessonId, nextCourseSectionEntity, emitter);
  225 + } catch (e) {
  226 + if (e is ApiException) {
  227 + showToast(e.message.toString());
  228 + }
  229 + return null;
  230 + }
  231 + }
  232 +
  233 + ///检查section是否锁定
  234 + Future<CourseSectionEntity?> checkCourseSectionLocked(int courseLessonId, CourseSectionEntity? courseSectionEntity,
  235 + Emitter<SectionState> emitter) async {
  236 + if (courseSectionEntity != null) {
  237 + if (courseSectionEntity.lock == false) {
  238 + ///如果section没锁,直接返回
  239 + return courseSectionEntity;
  240 + } else {
  241 + ///如果section锁了,请求当前unit下的section数据,查询解锁状态
  242 + int courseUnitId = courseSectionEntity.courseUnitId;
  243 + CourseSectionEntity? result = await loading(() async {
  244 + List<CourseSectionEntity>? tempSectionEntities =
  245 + await LessonDao.courseSection(courseUnitId: courseUnitId);
  246 + if (tempSectionEntities != null) {
  247 + _courseSectionDatasMap[courseUnitId] = tempSectionEntities;
  248 + emitter(LessonDataLoadState());
  249 + }
  250 + courseSectionEntity = tempSectionEntities?.firstWhereOrNull(
  251 + (element) => element.id == courseSectionEntity?.id);
  252 + if (courseSectionEntity?.lock == false) {
  253 + ///刷新后的数据如果解锁了,直接返回
  254 + return courseSectionEntity;
  255 + } else {
  256 + ///请求失败或者锁定状态没变(没变就感觉状态异常了,理论上不应该进入这条分支),返回null
  257 + showToast('下个课程还没解锁哦');
  258 + return null;
  259 + }
  260 + });
  261 + return result;
  262 + }
192 } else { 263 } else {
193 - ///section为空说明当前unit学完了,找下一个unit。(跨unit选lesson) 264 + ///section为空说明当前unit学完了,找下一个unit。(跨unit选section)
194 ///先根据courseLessonId找出当前的unit 265 ///先根据courseLessonId找出当前的unit
195 final curCourseUnitDetail = findCourseUnitDetailById(courseLessonId); 266 final curCourseUnitDetail = findCourseUnitDetailById(courseLessonId);
196 if (curCourseUnitDetail != null) { 267 if (curCourseUnitDetail != null) {
197 - ///再根据当前unit找出下一个unit  
198 - final nextCourseUnitDetail = _courseUnitEntity.courseUnitVOList  
199 - ?.firstWhere((element) =>  
200 - element.sortOrder == (curCourseUnitDetail.sortOrder! + 1)); 268 + ///再根据当前unit的sortOrder找出下一个unit
  269 + CourseUnitDetail? nextCourseUnitDetail =
  270 + _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
  271 + element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
  272 +
201 if (nextCourseUnitDetail != null) { 273 if (nextCourseUnitDetail != null) {
202 - final courseUnitId = nextCourseUnitDetail.id!;  
203 - try {  
204 - await loading(() async {  
205 - _courseSectionDatasMap[courseUnitId] =  
206 - await LessonDao.courseSection(courseUnitId: courseUnitId);  
207 - emitter(LessonDataLoadState()); 274 + if (nextCourseUnitDetail.lock == true) {
  275 + ///如果下一个unit是锁定状态,请求数据刷新查询解锁状态
  276 + CourseSectionEntity? result = await loading(() async {
  277 + CourseUnitEntity? newCourseUnitEntity = await LessonDao.courseUnit(
  278 + _courseUnitEntity.nowCourseModuleId);
  279 +
  280 + ///拿到重新获取到的unit后,再次判断是否解锁
  281 + nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList?.firstWhereOrNull(
  282 + (element) => element.id == nextCourseUnitDetail?.id);
  283 + if (nextCourseUnitDetail?.lock == false) {
  284 + ///解锁状态从锁定到解锁,覆盖原unit数据并刷新ui
  285 + _courseUnitEntity = newCourseUnitEntity!;
  286 + courseUnitEntityChanged = true;
  287 + emitter(LessonDataLoadState());
  288 +
  289 + return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail!.id!, emitter);
  290 + } else {
  291 + showToast('下个单元课程还没解锁哦');
  292 +
  293 + ///如果还是锁定状态,返回null
  294 + return null;
  295 + }
208 }); 296 });
209 - _pageController.nextPage(  
210 - duration: const Duration(milliseconds: 500),  
211 - curve: Curves.ease,  
212 - );  
213 - return _courseSectionDatasMap[courseUnitId]!.first;  
214 - } catch (e) {  
215 - if (e is ApiException) {  
216 - showToast(e.message.toString());  
217 - }  
218 - return null; 297 + return result;
  298 + } else {
  299 + return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail.id!, emitter);
219 } 300 }
220 } else { 301 } else {
  302 + showToast("恭喜你,本阶段学到顶啦");
221 ///最后一个unit了 303 ///最后一个unit了
222 return null; 304 return null;
223 } 305 }
@@ -227,4 +309,43 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -227,4 +309,43 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
227 } 309 }
228 } 310 }
229 } 311 }
  312 +
  313 + ///检查下一个unit的(第一个)section
  314 + Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(int courseLessonId, int nextCourseUnitDetailId,
  315 + Emitter<SectionState> emitter) async {
  316 + CourseSectionEntity? firstSectionNextUnit = await getFirstSectionByUnitId(
  317 + nextCourseUnitDetailId, emitter);
  318 + if (firstSectionNextUnit != null) {
  319 + ///下个unit的第一个section如果不为空,再次检查是否锁定
  320 + CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(courseLessonId, firstSectionNextUnit, emitter);
  321 + if (courseSectionEntity != null) {
  322 + ///只有是下一unit的第一个section并且解锁了,才跳转
  323 + _pageController.nextPage(
  324 + duration: const Duration(milliseconds: 250),
  325 + curve: Curves.ease,
  326 + );
  327 + }
  328 + return courseSectionEntity;
  329 + } else {
  330 + ///下个unit的第一个section如果为空,返回null
  331 + showToast('下个课程暂未找到');
  332 + return null;
  333 + }
  334 + }
  335 +
  336 + ///根据unitId获取当前unit的第一个section
  337 + Future<CourseSectionEntity?> getFirstSectionByUnitId(
  338 + int courseUnitId, Emitter<SectionState> emitter) async {
  339 + List<CourseSectionEntity>? courseSectionEntity =
  340 + _courseSectionDatasMap[courseUnitId];
  341 + if (courseSectionEntity == null) {
  342 + ///如果没下载过,请求数据
  343 + await loading(() async {
  344 + _courseSectionDatasMap[courseUnitId] =
  345 + await LessonDao.courseSection(courseUnitId: courseUnitId);
  346 + emitter(LessonDataLoadState());
  347 + });
  348 + }
  349 + return _courseSectionDatasMap[courseUnitId]?.first;
  350 + }
230 } 351 }
lib/pages/section/section_page.dart
@@ -33,8 +33,14 @@ class SectionPage extends StatelessWidget { @@ -33,8 +33,14 @@ class SectionPage extends StatelessWidget {
33 ?.indexWhere((element) => element.id == courseUnitId) ?? 33 ?.indexWhere((element) => element.id == courseUnitId) ??
34 0; 34 0;
35 return BlocProvider( 35 return BlocProvider(
36 - create: (context) => SectionBloc(courseUnitEntity, initialPage,  
37 - PageController(initialPage: initialPage), ScrollController()), 36 + create: (context) => SectionBloc(
  37 + courseUnitEntity,
  38 + initialPage,
  39 + PageController(initialPage: initialPage),
  40 + ScrollController(),
  41 + ScrollController()),
  42 + //为了触发指示器进入后计算位置
  43 + // ..add(CurrentUnitIndexChangeEvent(initialPage)),
38 child: _SectionPageView(context), 44 child: _SectionPageView(context),
39 ); 45 );
40 } 46 }
@@ -146,78 +152,86 @@ class _SectionPageView extends StatelessWidget { @@ -146,78 +152,86 @@ class _SectionPageView extends StatelessWidget {
146 children: [ 152 children: [
147 SectionHeaderWidget( 153 SectionHeaderWidget(
148 title: bloc.getCourseUnitDetail().name, 154 title: bloc.getCourseUnitDetail().name,
149 - courseModuleCode: bloc.courseUnitEntity.courseModuleCode), 155 + courseModuleCode: bloc.courseUnitEntity.courseModuleCode,
  156 + onBack: () {
  157 + popPage(data: {
  158 + 'needRefresh': bloc.courseUnitEntityChanged,
  159 + });
  160 + }),
150 Expanded( 161 Expanded(
151 - child: Container(  
152 - color: Colors.blue,  
153 - child: Padding(  
154 - padding: EdgeInsets.symmetric(horizontal: 10.w),  
155 - // child: OverflowBox(  
156 - child: NestedPageView.builder(  
157 - itemCount: bloc.unlockPageCount(),  
158 - controller: bloc.pageController,  
159 - onPageChanged: (int index) {  
160 - bloc.add(CurrentUnitIndexChangeEvent(index));  
161 - },  
162 - itemBuilder: (context, index) {  
163 - // return ScrollConfiguration(  
164 - // ///去掉 Android 上默认的边缘拖拽效果  
165 - // behavior: ScrollConfiguration.of(context)  
166 - // .copyWith(overscroll: false),  
167 - // child: _itemTransCard(  
168 - // bloc.getCourseUnitDetail(pageIndex: index),  
169 - // index,  
170 - // context),  
171 - // );  
172 - return _itemTransCard(  
173 - bloc.getCourseUnitDetail(pageIndex: index),  
174 - index,  
175 - context);  
176 - }),  
177 - // ), // 设置外部padding,  
178 - )  
179 - )  
180 - ), 162 + child: Padding(
  163 + padding: EdgeInsets.symmetric(horizontal: 10.w),
  164 + child: NestedPageView.builder(
  165 + itemCount: bloc.unlockPageCount(),
  166 + controller: bloc.pageController,
  167 + onPageChanged: (int index) {
  168 + bloc.add(CurrentUnitIndexChangeEvent(index));
  169 + },
  170 + itemBuilder: (context, index) {
  171 + return ScrollConfiguration(
  172 + ///去掉 Android 上默认的边缘拖拽效果
  173 + behavior: ScrollConfiguration.of(context)
  174 + .copyWith(overscroll: false),
  175 + child: _itemTransCard(
  176 + bloc.getCourseUnitDetail(pageIndex: index),
  177 + index,
  178 + context),
  179 + );
  180 + }),
  181 + )),
181 SafeArea( 182 SafeArea(
182 - child: Padding(  
183 - padding: EdgeInsets.symmetric(horizontal: 13.w),  
184 - child: Row(  
185 - mainAxisAlignment: MainAxisAlignment.spaceBetween,  
186 - children: [  
187 - SizedBox(  
188 - height: 47.h,  
189 - width: 80.w,  
190 - ),  
191 - Container(  
192 - decoration: BoxDecoration(  
193 - color: CourseModuleModel(  
194 - bloc.courseUnitEntity.courseModuleCode ??  
195 - 'Phase-1')  
196 - .color,  
197 - borderRadius: BorderRadius.circular(14.5.r),  
198 - ),  
199 - padding: EdgeInsets.symmetric(  
200 - vertical: 8.h, horizontal: 24.w),  
201 - child: Text(  
202 - '${bloc.currentPage + 1}/${bloc.unlockPageCount()}',  
203 - style: TextStyle(  
204 - color: Colors.white, fontSize: 12.sp),  
205 - ),  
206 - ),  
207 - Image.asset(  
208 - CourseModuleModel(  
209 - bloc.courseUnitEntity.courseModuleCode ??  
210 - 'Phase-1')  
211 - .courseModuleLogo  
212 - .assetPng,  
213 - height: 47.h,  
214 - width: 80.w,  
215 - // color: Colors.red,  
216 - ),  
217 - ],  
218 - ),  
219 - ),  
220 - ) 183 + child: SizedBox(
  184 + width: 210.w,
  185 + height: 40.h,
  186 + // child: OverflowBox(
  187 + // maxWidth: 300, // 允许内容超出容器宽度
  188 + child: ListView.builder(
  189 + controller: bloc.indicatorSrollController,
  190 + scrollDirection: Axis.horizontal,
  191 + itemCount: bloc.unlockPageCount(),
  192 + itemBuilder: (context, index) {
  193 + bool isSelected = index == bloc.currentPage;
  194 + // 计算透明度,使得当前页的指示器不透明,两侧逐渐透明
  195 + double opacity = 0.25 +
  196 + (1 - ((bloc.currentPage - index).abs() / 2)) *
  197 + 0.5;
  198 + // 限制透明度在 0.5 到 1.0 之间
  199 + opacity = opacity.clamp(0.25, 1.0);
  200 + return GestureDetector(
  201 + onTap: () => bloc.pageController.animateToPage(
  202 + index,
  203 + duration: const Duration(milliseconds: 60),
  204 + curve: Curves.ease),
  205 + child: SizedBox(
  206 + width: 30.0.w,
  207 + height: 30.0.h,
  208 + child: Padding(
  209 + padding:
  210 + EdgeInsets.all(isSelected ? 0.w : 5.w),
  211 + child: Opacity(
  212 + opacity: opacity,
  213 + child: Container(
  214 + alignment: Alignment.center,
  215 + decoration: BoxDecoration(
  216 + color: isSelected
  217 + ? Colors.blue
  218 + : Colors.grey,
  219 + shape: BoxShape.circle,
  220 + ),
  221 + child: Text("${index + 1}",
  222 + textAlign: TextAlign.center,
  223 + style: TextStyle(
  224 + color: Colors.white,
  225 + fontSize:
  226 + isSelected ? 12.sp : 8.sp)),
  227 + ),
  228 + ),
  229 + ),
  230 + ),
  231 + );
  232 + })),
  233 + ),
  234 + // )
221 ], 235 ],
222 ), 236 ),
223 ), 237 ),
@@ -252,62 +266,54 @@ Widget _itemTransCard( @@ -252,62 +266,54 @@ Widget _itemTransCard(
252 ), 266 ),
253 ); 267 );
254 } else { 268 } else {
255 - return  
256 - Padding(padding: EdgeInsets.symmetric(  
257 - vertical: 28.h),  
258 - child: Container(  
259 - color: Colors.red,  
260 - margin: EdgeInsets.symmetric(vertical: 10.h),  
261 - child: NestedListView.builder(  
262 - itemCount: bloc.courseSectionDatasMap[courseUnitDetail.id]?.length ?? 0,  
263 - scrollDirection: Axis.horizontal,  
264 - itemBuilder: (BuildContext context, int index) {  
265 - CourseSectionEntity sectionData = courseSectionEntities[index];  
266 - if (sectionData.courseType == SectionType.bouns.value) {  
267 - //彩蛋  
268 - return GestureDetector(  
269 - onTap: () {  
270 - if (!UserUtil.isLogined()) {  
271 - pushNamed(AppRouteName.login);  
272 - return;  
273 - }  
274 - if (sectionData.lock == true) {  
275 - showToast('当前课程暂未解锁');  
276 - return;  
277 - } 269 + return NestedListView.builder(
  270 + itemCount: bloc.courseSectionDatasMap[courseUnitDetail.id]?.length ?? 0,
  271 + scrollDirection: Axis.horizontal,
  272 + itemBuilder: (BuildContext context, int index) {
  273 + CourseSectionEntity sectionData = courseSectionEntities[index];
  274 + if (sectionData.courseType == SectionType.bouns.value) {
  275 + //彩蛋
  276 + return GestureDetector(
  277 + onTap: () {
  278 + if (!UserUtil.isLogined()) {
  279 + pushNamed(AppRouteName.login);
  280 + return;
  281 + }
  282 + if (sectionData.lock == true) {
  283 + showToast('当前课程暂未解锁');
  284 + return;
  285 + }
278 286
279 - ///进入课堂  
280 - bloc.add(RequestEnterClassEvent(  
281 - sectionData.id.toString(), sectionData.courseType));  
282 - },  
283 - child: SectionBoundsItem(  
284 - imageUrl: sectionData.coverUrl,  
285 - ),  
286 - );  
287 - } else {  
288 - return GestureDetector(  
289 - onTap: () {  
290 - if (!UserUtil.isLogined()) {  
291 - pushNamed(AppRouteName.login);  
292 - return;  
293 - }  
294 - if (sectionData.lock == true) {  
295 - showToast('当前课程暂未解锁');  
296 - return;  
297 - } 287 + ///进入课堂
  288 + bloc.add(RequestEnterClassEvent(
  289 + sectionData.id.toString(), sectionData.courseType));
  290 + },
  291 + child: SectionBoundsItem(
  292 + imageUrl: sectionData.coverUrl,
  293 + ),
  294 + );
  295 + } else {
  296 + return GestureDetector(
  297 + onTap: () {
  298 + if (!UserUtil.isLogined()) {
  299 + pushNamed(AppRouteName.login);
  300 + return;
  301 + }
  302 + if (sectionData.lock == true) {
  303 + showToast('当前课程暂未解锁');
  304 + return;
  305 + }
298 306
299 - ///进入课堂  
300 - bloc.add(RequestEnterClassEvent(  
301 - sectionData.id.toString(), sectionData.courseType));  
302 - },  
303 - child: SectionItem(  
304 - courseModuleId: bloc.courseUnitEntity.courseModuleCode,  
305 - lessons: sectionData,  
306 - ),  
307 - );  
308 - }  
309 - }),  
310 - ),  
311 - ); 307 + ///进入课堂
  308 + bloc.add(RequestEnterClassEvent(
  309 + sectionData.id.toString(), sectionData.courseType));
  310 + },
  311 + child: SectionItem(
  312 + courseModuleId: bloc.courseUnitEntity.courseModuleCode,
  313 + lessons: sectionData,
  314 + ),
  315 + );
  316 + }
  317 + });
312 } 318 }
313 } 319 }
lib/pages/section/widgets/section_header_widget.dart
@@ -7,12 +7,14 @@ import &#39;package:wow_english/pages/user/bloc/user_bloc.dart&#39;; @@ -7,12 +7,14 @@ import &#39;package:wow_english/pages/user/bloc/user_bloc.dart&#39;;
7 import '../courese_module_model.dart'; 7 import '../courese_module_model.dart';
8 8
9 class SectionHeaderWidget extends StatelessWidget { 9 class SectionHeaderWidget extends StatelessWidget {
10 - const SectionHeaderWidget({super.key, this.title, this.courseModuleCode}); 10 + const SectionHeaderWidget({super.key, this.title, this.courseModuleCode, this.onBack});
11 11
12 final String? title; 12 final String? title;
13 13
14 final String? courseModuleCode; 14 final String? courseModuleCode;
15 15
  16 + final VoidCallback? onBack;
  17 +
16 @override 18 @override
17 Widget build(BuildContext context) { 19 Widget build(BuildContext context) {
18 return BlocBuilder<UserBloc, UserState>( 20 return BlocBuilder<UserBloc, UserState>(
@@ -27,7 +29,11 @@ class SectionHeaderWidget extends StatelessWidget { @@ -27,7 +29,11 @@ class SectionHeaderWidget extends StatelessWidget {
27 ScreenUtil().bottomBarHeight.horizontalSpace, 29 ScreenUtil().bottomBarHeight.horizontalSpace,
28 GestureDetector( 30 GestureDetector(
29 onTap: () { 31 onTap: () {
30 - Navigator.pop(context); 32 + if (onBack == null) {
  33 + Navigator.pop(context);
  34 + } else {
  35 + onBack!();
  36 + }
31 }, 37 },
32 child: Container( 38 child: Container(
33 alignment: Alignment.center, 39 alignment: Alignment.center,
lib/pages/unit/bloc.dart
@@ -23,10 +23,10 @@ class UnitBloc extends Bloc&lt;UnitEvent, UnitState&gt; { @@ -23,10 +23,10 @@ class UnitBloc extends Bloc&lt;UnitEvent, UnitState&gt; {
23 23
24 24
25 UnitBloc(CourseModuleEntity? courseEntity) : super(UnitState().init()) { 25 UnitBloc(CourseModuleEntity? courseEntity) : super(UnitState().init()) {
26 - on<RequestUnitDataEvent>(_requestData); 26 + on<RequestUnitDataEvent>(_requestUnitDatas);
27 } 27 }
28 28
29 - void _requestData(RequestUnitDataEvent event, Emitter<UnitState> emitter) async { 29 + void _requestUnitDatas(RequestUnitDataEvent event, Emitter<UnitState> emitter) async {
30 try { 30 try {
31 await loading(() async { 31 await loading(() async {
32 _unitData = await LessonDao.courseUnit(event.moduleId); 32 _unitData = await LessonDao.courseUnit(event.moduleId);
lib/pages/unit/view.dart
@@ -57,7 +57,7 @@ class UnitPage extends StatelessWidget { @@ -57,7 +57,7 @@ class UnitPage extends StatelessWidget {
57 return GestureDetector( 57 return GestureDetector(
58 onTap: () { 58 onTap: () {
59 if (data.lock == true) { 59 if (data.lock == true) {
60 - showToast('当前课程暂未解锁'); 60 + showToast('当前单元课程暂未解锁');
61 return; 61 return;
62 } 62 }
63 63
@@ -65,7 +65,15 @@ class UnitPage extends StatelessWidget { @@ -65,7 +65,15 @@ class UnitPage extends StatelessWidget {
65 arguments: { 65 arguments: {
66 'courseUnitEntity': bloc.unitData, 66 'courseUnitEntity': bloc.unitData,
67 'courseUnitId': data.id 67 'courseUnitId': data.id
68 - }); 68 + }).then((value) {
  69 + if (value != null) {
  70 + Map<String, dynamic> dataMap = value as Map<String, dynamic>;
  71 + bool needRefresh = dataMap['needRefresh'];
  72 + if (needRefresh) {
  73 + bloc.add(RequestUnitDataEvent(courseModuleEntity?.id));
  74 + }
  75 + }
  76 + });
69 }, 77 },
70 child: CourseUnitItem( 78 child: CourseUnitItem(
71 unitEntity: bloc.unitData!, 79 unitEntity: bloc.unitData!,
lib/utils/list_ext.dart
@@ -8,4 +8,14 @@ extension ListExtension&lt;E&gt; on List&lt;E&gt; { @@ -8,4 +8,14 @@ extension ListExtension&lt;E&gt; on List&lt;E&gt; {
8 } 8 }
9 return null; 9 return null;
10 } 10 }
  11 +
  12 + /// 获取数组中第一个匹配元素,没有就返回null
  13 + E? firstWhereOrNull(bool Function(E element) test) {
  14 + for (E element in this) {
  15 + if (test(element)) {
  16 + return element;
  17 + }
  18 + }
  19 + return null;
  20 + }
11 } 21 }