Commit 8df5fbf92b47fa89a767ee8c5d97bcbd74e7b541

Authored by 吴启风
1 parent a9e0b001

feat:单元列表页由于ios嵌套滑动不生效,调整方案使用指示器标识,并支持指示器滑动以及点击联动翻页

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,6 +32,10 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -30,6 +32,10 @@ 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;
@@ -45,7 +51,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -45,7 +51,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
45 CourseProcessEntity? get processEntity => _processEntity; 51 CourseProcessEntity? get processEntity => _processEntity;
46 52
47 SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, 53 SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController,
48 - this._listController) 54 + this._listController, this._indicatorSrollController)
49 : super(LessonInitial()) { 55 : super(LessonInitial()) {
50 on<RequestDataEvent>(_requestSectionsData); 56 on<RequestDataEvent>(_requestSectionsData);
51 on<RequestEndClassEvent>(_requestEndClass); 57 on<RequestEndClassEvent>(_requestEndClass);
@@ -121,6 +127,28 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; { @@ -121,6 +127,28 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
121 void _pageControllerChange( 127 void _pageControllerChange(
122 CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async { 128 CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async {
123 _currentPage = event.unitIndex; 129 _currentPage = event.unitIndex;
  130 + double indicatorWidth = 30.0.w; // 指示器宽度
  131 +
  132 + // 计算选中项的偏移量
  133 + double offset = _currentPage * indicatorWidth -
  134 + (_indicatorSrollController.position.viewportDimension -
  135 + indicatorWidth) /
  136 + 2;
  137 +
  138 + // 确保偏移量在合理范围内
  139 + if (offset < 0) {
  140 + offset = 0;
  141 + } else if (offset >
  142 + unlockPageCount() * indicatorWidth -
  143 + _indicatorSrollController.position.viewportDimension) {
  144 + offset = unlockPageCount() * indicatorWidth -
  145 + _indicatorSrollController.position.viewportDimension;
  146 + }
  147 + _indicatorSrollController.animateTo(
  148 + offset,
  149 + duration: const Duration(milliseconds: 250),
  150 + curve: Curves.easeInOut,
  151 + );
124 emitter(CurrentPageIndexState()); 152 emitter(CurrentPageIndexState());
125 } 153 }
126 154
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 }
@@ -148,76 +154,79 @@ class _SectionPageView extends StatelessWidget { @@ -148,76 +154,79 @@ class _SectionPageView extends StatelessWidget {
148 title: bloc.getCourseUnitDetail().name, 154 title: bloc.getCourseUnitDetail().name,
149 courseModuleCode: bloc.courseUnitEntity.courseModuleCode), 155 courseModuleCode: bloc.courseUnitEntity.courseModuleCode),
150 Expanded( 156 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 - ), 157 + child: Padding(
  158 + padding: EdgeInsets.symmetric(horizontal: 10.w),
  159 + child: NestedPageView.builder(
  160 + itemCount: bloc.unlockPageCount(),
  161 + controller: bloc.pageController,
  162 + onPageChanged: (int index) {
  163 + bloc.add(CurrentUnitIndexChangeEvent(index));
  164 + },
  165 + itemBuilder: (context, index) {
  166 + return ScrollConfiguration(
  167 + ///去掉 Android 上默认的边缘拖拽效果
  168 + behavior: ScrollConfiguration.of(context)
  169 + .copyWith(overscroll: false),
  170 + child: _itemTransCard(
  171 + bloc.getCourseUnitDetail(pageIndex: index),
  172 + index,
  173 + context),
  174 + );
  175 + }),
  176 + )),
181 SafeArea( 177 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 - ) 178 + child: SizedBox(
  179 + width: 210.w,
  180 + height: 40.h,
  181 + // child: OverflowBox(
  182 + // maxWidth: 300, // 允许内容超出容器宽度
  183 + child: ListView.builder(
  184 + controller: bloc.indicatorSrollController,
  185 + scrollDirection: Axis.horizontal,
  186 + itemCount: bloc.unlockPageCount(),
  187 + itemBuilder: (context, index) {
  188 + bool isSelected = index == bloc.currentPage;
  189 + // 计算透明度,使得当前页的指示器不透明,两侧逐渐透明
  190 + double opacity = 0.25 +
  191 + (1 - ((bloc.currentPage - index).abs() / 2)) *
  192 + 0.5;
  193 + // 限制透明度在 0.5 到 1.0 之间
  194 + opacity = opacity.clamp(0.25, 1.0);
  195 + return GestureDetector(
  196 + onTap: () => bloc.pageController.animateToPage(
  197 + index,
  198 + duration: const Duration(milliseconds: 60),
  199 + curve: Curves.ease),
  200 + child: SizedBox(
  201 + width: 30.0.w,
  202 + height: 30.0.h,
  203 + child: Padding(
  204 + padding:
  205 + EdgeInsets.all(isSelected ? 0.w : 5.w),
  206 + child: Opacity(
  207 + opacity: opacity,
  208 + child: Container(
  209 + alignment: Alignment.center,
  210 + decoration: BoxDecoration(
  211 + color: isSelected
  212 + ? Colors.blue
  213 + : Colors.grey,
  214 + shape: BoxShape.circle,
  215 + ),
  216 + child: Text("${index + 1}",
  217 + textAlign: TextAlign.center,
  218 + style: TextStyle(
  219 + color: Colors.white,
  220 + fontSize:
  221 + isSelected ? 12.sp : 8.sp)),
  222 + ),
  223 + ),
  224 + ),
  225 + ),
  226 + );
  227 + })),
  228 + ),
  229 + // )
221 ], 230 ],
222 ), 231 ),
223 ), 232 ),
@@ -252,62 +261,54 @@ Widget _itemTransCard( @@ -252,62 +261,54 @@ Widget _itemTransCard(
252 ), 261 ),
253 ); 262 );
254 } else { 263 } 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 - } 264 + return NestedListView.builder(
  265 + itemCount: bloc.courseSectionDatasMap[courseUnitDetail.id]?.length ?? 0,
  266 + scrollDirection: Axis.horizontal,
  267 + itemBuilder: (BuildContext context, int index) {
  268 + CourseSectionEntity sectionData = courseSectionEntities[index];
  269 + if (sectionData.courseType == SectionType.bouns.value) {
  270 + //彩蛋
  271 + return GestureDetector(
  272 + onTap: () {
  273 + if (!UserUtil.isLogined()) {
  274 + pushNamed(AppRouteName.login);
  275 + return;
  276 + }
  277 + if (sectionData.lock == true) {
  278 + showToast('当前课程暂未解锁');
  279 + return;
  280 + }
278 281
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 - } 282 + ///进入课堂
  283 + bloc.add(RequestEnterClassEvent(
  284 + sectionData.id.toString(), sectionData.courseType));
  285 + },
  286 + child: SectionBoundsItem(
  287 + imageUrl: sectionData.coverUrl,
  288 + ),
  289 + );
  290 + } else {
  291 + return GestureDetector(
  292 + onTap: () {
  293 + if (!UserUtil.isLogined()) {
  294 + pushNamed(AppRouteName.login);
  295 + return;
  296 + }
  297 + if (sectionData.lock == true) {
  298 + showToast('当前课程暂未解锁');
  299 + return;
  300 + }
298 301
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 - ); 302 + ///进入课堂
  303 + bloc.add(RequestEnterClassEvent(
  304 + sectionData.id.toString(), sectionData.courseType));
  305 + },
  306 + child: SectionItem(
  307 + courseModuleId: bloc.courseUnitEntity.courseModuleCode,
  308 + lessons: sectionData,
  309 + ),
  310 + );
  311 + }
  312 + });
312 } 313 }
313 } 314 }