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 16  
17 17 void _init(InitEvent event, Emitter<HomeState> emit) async {
18 18 await _checkUpdate(emit);
19   - debugPrint('WQF ModuleSelectBloc _init');
20 19 }
21 20  
22 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 13  
14 14 import '../../common/core/user_util.dart';
15 15 import '../../common/dialogs/show_dialog.dart';
16   -import '../../utils/log_util.dart';
17 16 import 'bloc.dart';
18 17 import 'event.dart';
19 18 import 'package:flutter_screenutil/flutter_screenutil.dart';
... ... @@ -38,11 +37,9 @@ class _HomePageView extends StatelessWidget {
38 37 Widget build(BuildContext context) {
39 38 return MultiBlocListener(listeners: [
40 39 BlocListener<UserBloc, UserState>(listener: (context, state) {
41   - debugPrint('WQF ModuleSelectPage BlocListener state: $state');
42 40 }),
43 41 BlocListener<HomeBloc, HomeState>(
44 42 listener: (context, state) {
45   - Log.d("WQF HomePage listener state: $state");
46 43 if (state is UpdateDialogState) {
47 44 _showUpdateDialog(context, state.forceUpdate, state.appVersionEntity);
48 45 }
... ... @@ -97,8 +94,6 @@ class _HomePageView extends StatelessWidget {
97 94 Expanded(
98 95 child: BlocBuilder<UserBloc, UserState>(
99 96 builder: (context, userState) {
100   - debugPrint(
101   - 'WQF ModuleSelectPage BlocBuilder state: $userState');
102 97 return GestureDetector(
103 98 onTap: () {
104 99 _checkPermission(() {
... ...
lib/pages/practice/bloc/topic_picture_bloc.dart
... ... @@ -103,7 +103,7 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt
103 103 } else {
104 104 // 答对后且播放完自动翻页
105 105 pageController.nextPage(
106   - duration: const Duration(milliseconds: 500),
  106 + duration: const Duration(milliseconds: 250),
107 107 curve: Curves.ease,
108 108 );
109 109 }
... ... @@ -332,7 +332,6 @@ class TopicPictureBloc extends BaseSectionBloc&lt;TopicPictureEvent, TopicPictureSt
332 332 'nextSection': true
333 333 });
334 334 }, againSectionTap: () {
335   - debugPrint("WQF 重做");
336 335 pageController.jumpToPage(0);
337 336 });
338 337 }
... ...
lib/pages/reading/bloc/reading_bloc.dart
... ... @@ -287,7 +287,7 @@ class ReadingPageBloc
287 287 } else {
288 288 _currentPage += 1;
289 289 pageController.nextPage(
290   - duration: const Duration(milliseconds: 500),
  290 + duration: const Duration(milliseconds: 250),
291 291 curve: Curves.ease,
292 292 );
293 293 }
... ...
lib/pages/section/bloc/section_bloc.dart
1 1 import 'package:flutter/cupertino.dart';
2 2 import 'package:flutter/foundation.dart';
  3 +import 'package:flutter/material.dart';
3 4 import 'package:flutter_bloc/flutter_bloc.dart';
  5 +import 'package:flutter_screenutil/flutter_screenutil.dart';
4 6 import 'package:wow_english/common/request/dao/lesson_dao.dart';
5 7 import 'package:wow_english/common/request/exception.dart';
6 8 import 'package:wow_english/common/request/dao/listen_dao.dart';
... ... @@ -13,10 +15,10 @@ import &#39;../../../models/course_unit_entity.dart&#39;;
13 15 import '../../../utils/list_ext.dart';
14 16  
15 17 part 'section_event.dart';
  18 +
16 19 part 'section_state.dart';
17 20  
18 21 class SectionBloc extends Bloc<SectionEvent, SectionState> {
19   -
20 22 PageController _pageController;
21 23  
22 24 PageController get pageController => _pageController;
... ... @@ -30,6 +32,10 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
30 32  
31 33 ScrollController get listController => _listController;
32 34  
  35 + ScrollController _indicatorSrollController;
  36 +
  37 + ScrollController get indicatorSrollController => _indicatorSrollController;
  38 +
33 39 CourseUnitEntity _courseUnitEntity;
34 40  
35 41 CourseUnitEntity get courseUnitEntity => _courseUnitEntity;
... ... @@ -45,7 +51,7 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
45 51 CourseProcessEntity? get processEntity => _processEntity;
46 52  
47 53 SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController,
48   - this._listController)
  54 + this._listController, this._indicatorSrollController)
49 55 : super(LessonInitial()) {
50 56 on<RequestDataEvent>(_requestSectionsData);
51 57 on<RequestEndClassEvent>(_requestEndClass);
... ... @@ -121,6 +127,28 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
121 127 void _pageControllerChange(
122 128 CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async {
123 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 152 emitter(CurrentPageIndexState());
125 153 }
126 154  
... ...
lib/pages/section/section_page.dart
... ... @@ -33,8 +33,14 @@ class SectionPage extends StatelessWidget {
33 33 ?.indexWhere((element) => element.id == courseUnitId) ??
34 34 0;
35 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 44 child: _SectionPageView(context),
39 45 );
40 46 }
... ... @@ -148,76 +154,79 @@ class _SectionPageView extends StatelessWidget {
148 154 title: bloc.getCourseUnitDetail().name,
149 155 courseModuleCode: bloc.courseUnitEntity.courseModuleCode),
150 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 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 261 ),
253 262 );
254 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 }
... ...