Blame view

lib/pages/section/bloc/section_bloc.dart 12.4 KB
d35a4e87   liangchengyou   feat:磨耳朵功能UI
1
  import 'package:flutter/cupertino.dart';
8988aa69   liangchengyou   feat:首页+课程列表数据获取
2
  import 'package:flutter/foundation.dart';
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
3
  import 'package:flutter/material.dart';
d35a4e87   liangchengyou   feat:磨耳朵功能UI
4
  import 'package:flutter_bloc/flutter_bloc.dart';
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
5
  import 'package:flutter_screenutil/flutter_screenutil.dart';
2187c85f   吴启风   feat:课程结构调整
6
  import 'package:wow_english/common/request/dao/lesson_dao.dart';
13e6d11d   liangchengyou   feat:首页课程模块接口
7
  import 'package:wow_english/common/request/exception.dart';
3c1d5c64   liangchengyou   feat:练习功能完成
8
9
  import 'package:wow_english/common/request/dao/listen_dao.dart';
  import 'package:wow_english/models/course_process_entity.dart';
13e6d11d   liangchengyou   feat:首页课程模块接口
10
  import 'package:wow_english/utils/loading.dart';
c61b3c1a   Key   feat: toast_util....
11
  import 'package:wow_english/utils/toast_util.dart';
60e47f7c   liangchengyou   feat:课程选择功能
12
  
2187c85f   吴启风   feat:课程结构调整
13
14
  import '../../../models/course_section_entity.dart';
  import '../../../models/course_unit_entity.dart';
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
15
  import '../../../utils/list_ext.dart';
60e47f7c   liangchengyou   feat:课程选择功能
16
  
2187c85f   吴启风   feat:课程结构调整
17
  part 'section_event.dart';
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
18
  
2187c85f   吴启风   feat:课程结构调整
19
  part 'section_state.dart';
8988aa69   liangchengyou   feat:首页+课程列表数据获取
20
  
2187c85f   吴启风   feat:课程结构调整
21
  class SectionBloc extends Bloc<SectionEvent, SectionState> {
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
22
23
24
25
26
27
28
29
30
31
32
33
34
    PageController _pageController;
  
    PageController get pageController => _pageController;
  
    ///当前页索引
    int _currentPage = 0;
  
    int get currentPage => _currentPage;
  
    ScrollController _listController;
  
    ScrollController get listController => _listController;
  
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
35
36
37
38
    ScrollController _indicatorSrollController;
  
    ScrollController get indicatorSrollController => _indicatorSrollController;
  
2187c85f   吴启风   feat:课程结构调整
39
40
41
42
    CourseUnitEntity _courseUnitEntity;
  
    CourseUnitEntity get courseUnitEntity => _courseUnitEntity;
  
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
43
44
    ///courseUnitId与课程环节列表的映射
    final Map<int, List<CourseSectionEntity>?> _courseSectionDatasMap = {};
2187c85f   吴启风   feat:课程结构调整
45
  
1ebed821   吴启风   feat:修复所有unit解锁态下...
46
47
    Map<int, List<CourseSectionEntity>?> get courseSectionDatasMap =>
        _courseSectionDatasMap;
8988aa69   liangchengyou   feat:首页+课程列表数据获取
48
  
3c1d5c64   liangchengyou   feat:练习功能完成
49
50
51
52
    CourseProcessEntity? _processEntity;
  
    CourseProcessEntity? get processEntity => _processEntity;
  
1ebed821   吴启风   feat:修复所有unit解锁态下...
53
    SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController,
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
54
        this._listController, this._indicatorSrollController)
22f36232   吴启风   feat:过渡页-练习环节
55
        : super(LessonInitial()) {
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
56
      on<RequestDataEvent>(_requestSectionsData);
46675a89   吴启风   feat:过渡页-视频环节
57
      on<RequestEndClassEvent>(_requestEndClass);
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
58
      on<RequestEnterClassEvent>(_requestEnterClass);
3c1d5c64   liangchengyou   feat:练习功能完成
59
      on<RequestVideoLessonEvent>(_requestVideoLesson);
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
60
      on<CurrentUnitIndexChangeEvent>(_pageControllerChange);
60e47f7c   liangchengyou   feat:课程选择功能
61
    }
13e6d11d   liangchengyou   feat:首页课程模块接口
62
  
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
63
    void _requestSectionsData(
22f36232   吴启风   feat:过渡页-练习环节
64
        RequestDataEvent event, Emitter<SectionState> emitter) async {
993c1a04   liangchengyou   feat:添加数据模型
65
66
      try {
        await loading(() async {
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
67
68
          _courseSectionDatasMap[event.courseUnitId] =
              await LessonDao.courseSection(courseUnitId: event.courseUnitId);
2187c85f   吴启风   feat:课程结构调整
69
          emitter(LessonDataLoadState());
993c1a04   liangchengyou   feat:添加数据模型
70
71
72
        });
      } catch (e) {
        if (e is ApiException) {
c61b3c1a   Key   feat: toast_util....
73
          showToast(e.message.toString());
13e6d11d   liangchengyou   feat:首页课程模块接口
74
75
76
        }
      }
    }
3c1d5c64   liangchengyou   feat:练习功能完成
77
  
22f36232   吴启风   feat:过渡页-练习环节
78
79
    void _requestVideoLesson(
        RequestVideoLessonEvent event, Emitter<SectionState> emitter) async {
3c1d5c64   liangchengyou   feat:练习功能完成
80
81
82
      try {
        await loading(() async {
          _processEntity = await ListenDao.process(event.courseLessonId);
22f36232   吴启风   feat:过渡页-练习环节
83
84
          emitter(
              RequestVideoLessonState(event.courseLessonId, event.courseType));
3c1d5c64   liangchengyou   feat:练习功能完成
85
86
87
        });
      } catch (e) {
        if (e is ApiException) {
22f36232   吴启风   feat:过渡页-练习环节
88
          showToast(e.message ?? '请求失败,请检查网络连接');
3c1d5c64   liangchengyou   feat:练习功能完成
89
90
91
        }
      }
    }
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
92
  
22f36232   吴启风   feat:过渡页-练习环节
93
94
    void _requestEnterClass(
        RequestEnterClassEvent event, Emitter<SectionState> emitter) async {
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
95
96
      try {
        await loading(() async {
22f36232   吴启风   feat:过渡页-练习环节
97
98
          await ListenDao.enterClass(event.courseLessonId);
          emitter(RequestEnterClassState(event.courseLessonId, event.courseType));
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
99
100
101
        });
      } catch (e) {
        if (e is ApiException) {
22f36232   吴启风   feat:过渡页-练习环节
102
          showToast(e.message ?? '请求失败,请检查网络连接');
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
103
104
105
106
        }
      }
    }
  
22f36232   吴启风   feat:过渡页-练习环节
107
108
    void _requestEndClass(
        RequestEndClassEvent event, Emitter<SectionState> emitter) async {
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
109
110
      if (event.isCompleted) {
        await await ListenDao.endClass(event.courseLessonId,
1ebed821   吴启风   feat:修复所有unit解锁态下...
111
            currentStep: event.currentStep, currentTime: event.currentTime);
22f36232   吴启风   feat:过渡页-练习环节
112
      } else {
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
113
        await await ListenDao.exitClass(event.courseLessonId,
1ebed821   吴启风   feat:修复所有unit解锁态下...
114
            currentStep: event.currentStep, currentTime: event.currentTime);
22f36232   吴启风   feat:过渡页-练习环节
115
      }
46675a89   吴启风   feat:过渡页-视频环节
116
      if (event.autoNextSection) {
22f36232   吴启风   feat:过渡页-练习环节
117
        final nextCourseSection =
1ebed821   吴启风   feat:修复所有unit解锁态下...
118
            await getNextCourseSection(int.parse(event.courseLessonId), emitter);
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
119
120
        if (nextCourseSection != null) {
          ///进入课堂
1ebed821   吴启风   feat:修复所有unit解锁态下...
121
122
          add(RequestEnterClassEvent(
              nextCourseSection.id.toString(), nextCourseSection.courseType));
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
123
        }
46675a89   吴启风   feat:过渡页-视频环节
124
      }
934e2b47   liangchengyou   feat:权限调整+课程进度接口对接
125
    }
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
126
  
22f36232   吴启风   feat:过渡页-练习环节
127
128
    void _pageControllerChange(
        CurrentUnitIndexChangeEvent event, Emitter<SectionState> emitter) async {
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
129
      _currentPage = event.unitIndex;
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
      double indicatorWidth = 30.0.w; // 指示器宽度
  
      // 计算选中项的偏移量
      double offset = _currentPage * indicatorWidth -
          (_indicatorSrollController.position.viewportDimension -
                  indicatorWidth) /
              2;
  
      // 确保偏移量在合理范围内
      if (offset < 0) {
        offset = 0;
      } else if (offset >
          unlockPageCount() * indicatorWidth -
              _indicatorSrollController.position.viewportDimension) {
        offset = unlockPageCount() * indicatorWidth -
            _indicatorSrollController.position.viewportDimension;
      }
      _indicatorSrollController.animateTo(
        offset,
        duration: const Duration(milliseconds: 250),
        curve: Curves.easeInOut,
      );
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
152
153
154
      emitter(CurrentPageIndexState());
    }
  
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
155
    ///未锁定的页(单元)数
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
156
    int unlockPageCount() {
22f36232   吴启风   feat:过渡页-练习环节
157
158
      return _courseUnitEntity.courseUnitVOList
              ?.indexWhereOrNull((element) => element.lock == true) ??
1ebed821   吴启风   feat:修复所有unit解锁态下...
159
160
          _courseUnitEntity.courseUnitVOList?.length ??
          0;
3ba925a9   吴启风   feat:环节页增加翻页切换单元效果
161
    }
46675a89   吴启风   feat:过渡页-视频环节
162
  
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
163
    ///当前页的课程详情
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    CourseUnitDetail getCourseUnitDetail({int? pageIndex}) {
      return _courseUnitEntity.courseUnitVOList![pageIndex ?? _currentPage];
    }
  
    ///根据courseLessonId查找对应的courseSection
    CourseSectionEntity? findCourseSectionById(int courseLessonId) {
      for (var entry in courseSectionDatasMap.entries) {
        var sectionList = entry.value;
        if (sectionList != null) {
          for (var section in sectionList) {
            if (section.id == courseLessonId) {
              return section;
            }
          }
        }
      }
      return null;
    }
  
    ///根据sortOrder查找对应的courseSection
    CourseSectionEntity? findCourseSectionBySort(int sortOrder) {
      for (var entry in courseSectionDatasMap.entries) {
        var sectionList = entry.value;
        if (sectionList != null) {
          for (var section in sectionList) {
            if (section.sortOrder == sortOrder) {
              return section;
            }
          }
        }
      }
      return null;
    }
  
    ///根据courseLessonId查找对应的CourseUnitDetail
    CourseUnitDetail? findCourseUnitDetailById(int courseLessonId) {
      final curCourseSectionEntity = findCourseSectionById(courseLessonId);
      if (curCourseSectionEntity != null) {
1ebed821   吴启风   feat:修复所有unit解锁态下...
202
        final curCourseUnitDetail = _courseUnitEntity.courseUnitVOList
009cf00d   吴启风   feat:环节&单元解锁逻辑
203
            ?.firstWhereOrNull(
1ebed821   吴启风   feat:修复所有unit解锁态下...
204
                (element) => element.id == curCourseSectionEntity.courseUnitId);
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
205
206
207
208
209
        return curCourseUnitDetail;
      }
      return null;
    }
  
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
210
    ///根据courseLessonId查找下一个courseSection
1ebed821   吴启风   feat:修复所有unit解锁态下...
211
212
    Future<CourseSectionEntity?> getNextCourseSection(
        int courseLessonId, Emitter<SectionState> emitter) async {
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
213
214
      final curCourseSectionEntity = findCourseSectionById(courseLessonId);
      final curSectionSort = curCourseSectionEntity?.sortOrder ?? 0;
1ebed821   吴启风   feat:修复所有unit解锁态下...
215
  
009cf00d   吴启风   feat:环节&单元解锁逻辑
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
      try {
        ///查找当前unit的下一个section
        CourseSectionEntity? nextCourseSectionEntity =
            findCourseSectionBySort(curSectionSort + 1);
        return checkCourseSectionLocked(courseLessonId, nextCourseSectionEntity, emitter);
      } catch (e) {
        if (e is ApiException) {
          showToast(e.message.toString());
        }
        return null;
      }
    }
  
    ///检查section是否锁定
    Future<CourseSectionEntity?> checkCourseSectionLocked(int courseLessonId, CourseSectionEntity? courseSectionEntity,
        Emitter<SectionState> emitter) async {
      if (courseSectionEntity != null) {
        if (courseSectionEntity.lock == false) {
          ///如果section没锁,直接返回
          return courseSectionEntity;
        } else {
          ///如果section锁了,请求当前unit下的section数据,查询解锁状态
          int courseUnitId = courseSectionEntity.courseUnitId;
          await loading(() async {
            List<CourseSectionEntity>? tempSectionEntities =
            await LessonDao.courseSection(courseUnitId: courseUnitId);
            if (tempSectionEntities != null) {
              _courseSectionDatasMap[courseUnitId] = tempSectionEntities;
              emitter(LessonDataLoadState());
            }
            courseSectionEntity = tempSectionEntities?.firstWhereOrNull(
                    (element) => element.id == courseSectionEntity?.id);
            if (courseSectionEntity?.lock == false) {
              ///刷新后的数据如果解锁了,直接返回
              return courseSectionEntity;
            } else {
              ///请求失败或者锁定状态没变(没变就感觉状态异常了,理论上不应该进入这条分支),返回null
              showToast('下个课程还没解锁哦');
              return null;
            }
          });
        }
46675a89   吴启风   feat:过渡页-视频环节
258
      } else {
009cf00d   吴启风   feat:环节&单元解锁逻辑
259
        ///section为空说明当前unit学完了,找下一个unit。(跨unit选section)
1ebed821   吴启风   feat:修复所有unit解锁态下...
260
        ///先根据courseLessonId找出当前的unit
66a7e3e7   吴启风   feat:退出课堂和结束课堂优化
261
        final curCourseUnitDetail = findCourseUnitDetailById(courseLessonId);
46675a89   吴启风   feat:过渡页-视频环节
262
        if (curCourseUnitDetail != null) {
009cf00d   吴启风   feat:环节&单元解锁逻辑
263
264
265
266
267
          ///再根据当前unit的sortOrder找出下一个unit
          CourseUnitDetail? nextCourseUnitDetail =
          _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
            element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
  
46675a89   吴启风   feat:过渡页-视频环节
268
          if (nextCourseUnitDetail != null) {
009cf00d   吴启风   feat:环节&单元解锁逻辑
269
270
            if (nextCourseUnitDetail.lock == true) {
              ///如果下一个unit是锁定状态,请求数据刷新查询解锁状态
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
271
              await loading(() async {
009cf00d   吴启风   feat:环节&单元解锁逻辑
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
                CourseUnitEntity? newCourseUnitEntity = await LessonDao.courseUnit(
                    _courseUnitEntity.nowCourseModuleId);
  
                ///拿到重新获取到的unit后,再次判断是否解锁
                nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList?.firstWhereOrNull(
                        (element) => element.id == nextCourseUnitDetail?.id);
                if (nextCourseUnitDetail?.lock == false) {
                  ///解锁状态从锁定到解锁,覆盖原unit数据并刷新ui
                  _courseUnitEntity = newCourseUnitEntity!;
                  emitter(LessonDataLoadState());
  
                  return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail!.id!, emitter);
                } else {
                  showToast('下个单元课程还没解锁哦');
  
                  ///如果还是锁定状态,返回null
                  return null;
                }
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
290
              });
009cf00d   吴启风   feat:环节&单元解锁逻辑
291
292
            } else {
              return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail.id!, emitter);
336074ab   吴启风   feat:过渡页-绘本环节跨单元处理
293
            }
46675a89   吴启风   feat:过渡页-视频环节
294
          } else {
009cf00d   吴启风   feat:环节&单元解锁逻辑
295
            showToast("恭喜你,本阶段学到顶啦");
46675a89   吴启风   feat:过渡页-视频环节
296
297
298
299
300
301
302
303
            ///最后一个unit了
            return null;
          }
        } else {
          ///找不到对应的unitDetail,理论上不可能
          return null;
        }
      }
009cf00d   吴启风   feat:环节&单元解锁逻辑
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
      return null;
    }
  
    ///检查下一个unit的(第一个)section
    Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(int courseLessonId, int nextCourseUnitDetailId,
        Emitter<SectionState> emitter) async {
      CourseSectionEntity? firstSectionNextUnit = await getFirstSectionByUnitId(
          nextCourseUnitDetailId, emitter);
      if (firstSectionNextUnit != null) {
        ///下个unit的第一个section如果不为空,再次检查是否锁定
        CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(courseLessonId, firstSectionNextUnit, emitter);
        if (courseSectionEntity != null) {
          ///只有是下一unit的第一个section并且解锁了,才跳转
          _pageController.nextPage(
            duration: const Duration(milliseconds: 250),
            curve: Curves.ease,
          );
        }
        return courseSectionEntity;
      } else {
        ///下个unit的第一个section如果为空,返回null
        showToast('下个课程暂未找到');
        return null;
      }
    }
  
    ///根据unitId获取当前unit的第一个section
    Future<CourseSectionEntity?> getFirstSectionByUnitId(
        int courseUnitId, Emitter<SectionState> emitter) async {
      List<CourseSectionEntity>? courseSectionEntity =
          _courseSectionDatasMap[courseUnitId];
      if (courseSectionEntity == null) {
        ///如果没下载过,请求数据
        await loading(() async {
          _courseSectionDatasMap[courseUnitId] =
              await LessonDao.courseSection(courseUnitId: courseUnitId);
          emitter(LessonDataLoadState());
        });
      }
      return _courseSectionDatasMap[courseUnitId]?.first;
46675a89   吴启风   feat:过渡页-视频环节
344
    }
60e47f7c   liangchengyou   feat:课程选择功能
345
  }