Blame view

lib/pages/practice/bloc/topic_picture_bloc.dart 11.4 KB
45c862fd   吴启风   feat:闯关交互优化-答错晃动、...
1
2
  import 'dart:async';
  
2eb67dd4   liangchengyou   feat:调整代码
3
  import 'package:audioplayers/audioplayers.dart';
688b7b5c   liangchengyou   feat:更新文件结构
4
  import 'package:flutter/cupertino.dart';
2eb67dd4   liangchengyou   feat:调整代码
5
  import 'package:flutter/foundation.dart';
688b7b5c   liangchengyou   feat:更新文件结构
6
  import 'package:flutter_bloc/flutter_bloc.dart';
2eb67dd4   liangchengyou   feat:调整代码
7
  import 'package:flutter_easyloading/flutter_easyloading.dart';
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
8
  import 'package:permission_handler/permission_handler.dart';
2eb67dd4   liangchengyou   feat:调整代码
9
10
  import 'package:wow_english/common/request/dao/listen_dao.dart';
  import 'package:wow_english/common/request/exception.dart';
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
11
  import 'package:wow_english/common/speech/chivox_evaluation_channel.dart';
2eb67dd4   liangchengyou   feat:调整代码
12
  import 'package:wow_english/models/course_process_entity.dart';
22f36232   吴启风   feat:过渡页-练习环节
13
14
15
  import 'package:wow_english/pages/section/subsection/base_section/bloc.dart';
  import 'package:wow_english/pages/section/subsection/base_section/event.dart';
  import 'package:wow_english/pages/section/subsection/base_section/state.dart';
c623c7b2   吴启风   feat:语音跟读作答结果动效&语音
16
  import 'package:wow_english/utils/audio_player_util.dart';
3c1d5c64   liangchengyou   feat:练习功能完成
17
  import 'package:wow_english/utils/toast_util.dart';
7652f701   liangchengyou   feat:课程购买UI逻辑
18
  
e811f164   吴启风   feat:权限申请页面增加隐私合规...
19
  import '../../../common/permission/permissionRequester.dart';
642081ad   吴启风   feat:lottie动画加载优化...
20
  import '../../../common/utils/show_star_reward_dialog.dart';
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
21
  import '../../../models/voice_result_type.dart';
22f36232   吴启风   feat:过渡页-练习环节
22
  import '../../../route/route.dart';
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
23
  import '../../../utils/log_util.dart';
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
24
  
7652f701   liangchengyou   feat:课程购买UI逻辑
25
  part 'topic_picture_event.dart';
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
26
  
688b7b5c   liangchengyou   feat:更新文件结构
27
  part 'topic_picture_state.dart';
7652f701   liangchengyou   feat:课程购买UI逻辑
28
  
1f5969b8   biao   修复 练习和绘本播放音频问题
29
  class TopicPictureBloc
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
30
31
      extends BaseSectionBloc<TopicPictureEvent, TopicPictureState>
      with WidgetsBindingObserver {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
32
33
    final PageController pageController;
  
608c05b4   liangchengyou   feat:兑换课程
34
    final String courseLessonId;
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
35
36
37
  
    int _currentPage = 0;
  
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
38
    int _optionSelectItem = -1;
2eb67dd4   liangchengyou   feat:调整代码
39
40
41
  
    CourseProcessEntity? _entity;
  
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
42
43
    CourseProcessEntity? get entity => _entity;
  
2eb67dd4   liangchengyou   feat:调整代码
44
    ///正在评测
d4d91cb0   吴启风   feat:lottie动效组件封装...
45
    bool _isRecording = false;
2eb67dd4   liangchengyou   feat:调整代码
46
  
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
47
48
    int get currentPage => _currentPage + 1;
  
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
49
50
    /// 选择题选中项
    int get optionSelectItem => _optionSelectItem;
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
51
  
d4d91cb0   吴启风   feat:lottie动效组件封装...
52
    bool get isRecording => _isRecording;
2eb67dd4   liangchengyou   feat:调整代码
53
  
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
54
    final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel();
2eb67dd4   liangchengyou   feat:调整代码
55
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
56
    late AudioPlayer audioPlayer;
2eb67dd4   liangchengyou   feat:调整代码
57
  
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
58
59
    final BuildContext context;
  
07b173c9   吴启风   feat:练习页上方进度信息改成进...
60
61
    final Color? moduleColor;
  
8fb5f860   吴启风   feat:先声录音状态优化
62
63
    final String TAG = 'TopicPictureBloc';
  
07b173c9   吴启风   feat:练习页上方进度信息改成进...
64
65
    TopicPictureBloc(
        this.context, this.pageController, this.courseLessonId, this.moduleColor)
1f5969b8   biao   修复 练习和绘本播放音频问题
66
        : super(TopicPictureInitial()) {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
67
      on<CurrentPageIndexChangeEvent>(_pageControllerChange);
3c1d5c64   liangchengyou   feat:练习功能完成
68
69
      on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
      on<XSVoiceResultEvent>(_voiceXsResult);
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
70
      on<SelectItemEvent>(_selectItemLoad);
5989d250   吴启风   feat:体验优化-选择题选中边框...
71
      on<SelectItemResetEvent>(_selectItemReset);
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
72
      on<RequestDataEvent>(_requestData);
ae77d87f   吴启风   feat:fix语音题无法手动停止...
73
      on<XSVoiceStartEvent>(_voiceXsStart);
3c1d5c64   liangchengyou   feat:练习功能完成
74
      on<XSVoiceStopEvent>(_voiceXsStop);
aeafd474   吴启风   feat:选择题作答后播放音效&答...
75
      on<VoicePlayEvent>(_questionVoicePlay);
8fb5f860   吴启风   feat:先声录音状态优化
76
      on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange);
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
77
      on<InitBlocEvent>((event, emit) async {
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
78
79
        //音频播放器
        audioPlayer = AudioPlayer();
3c1d5c64   liangchengyou   feat:练习功能完成
80
        audioPlayer.onPlayerStateChanged.listen((event) async {
8fb5f860   吴启风   feat:先声录音状态优化
81
          Log.d('$TAG onPlayerStateChanged event=$event');
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
82
83
84
85
          if (isClosed) {
            return;
          }
          add(VoicePlayStateChangeEvent());
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
86
87
        });
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
88
        methodChannel.setMethodCallHandler((call) async {
1f5969b8   biao   修复 练习和绘本播放音频问题
89
90
91
92
          if (call.method == 'voiceResult') {
            //评测结果
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
93
            add(XSVoiceResultEvent(call.arguments));
b90a1518   liangchengyou   feat:练习接口逻辑完成
94
95
96
            return;
          }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
97
98
          if (call.method == 'voiceStart') {
            //评测开始
b90a1518   liangchengyou   feat:练习接口逻辑完成
99
100
101
            if (kDebugMode) {
              print('评测开始');
            }
8fb5f860   吴启风   feat:先声录音状态优化
102
103
            _isRecording = true;
            add(OnXSVoiceStateChangeEvent());
b90a1518   liangchengyou   feat:练习接口逻辑完成
104
105
106
            return;
          }
  
8fb5f860   吴启风   feat:先声录音状态优化
107
          if (call.method == 'voiceEnd' || call.method == 'voiceCancel') {
1f5969b8   biao   修复 练习和绘本播放音频问题
108
109
110
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
            //评测结束
b90a1518   liangchengyou   feat:练习接口逻辑完成
111
            if (kDebugMode) {
8fb5f860   吴启风   feat:先声录音状态优化
112
              print(call.method == 'voiceEnd' ? '评测结束' : '评测取消');
b90a1518   liangchengyou   feat:练习接口逻辑完成
113
            }
8fb5f860   吴启风   feat:先声录音状态优化
114
115
            _isRecording = false;
            add(OnXSVoiceStateChangeEvent());
b90a1518   liangchengyou   feat:练习接口逻辑完成
116
117
118
            return;
          }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
119
120
121
122
123
          if (call.method == 'voiceFail') {
            //评测失败
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
  
8fb5f860   吴启风   feat:先声录音状态优化
124
125
            _isRecording = false;
            add(OnXSVoiceStateChangeEvent());
b90a1518   liangchengyou   feat:练习接口逻辑完成
126
127
            EasyLoading.showToast('评测失败');
            return;
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
128
129
          }
        });
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
130
  
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
131
132
        await methodChannel.invokeMethod('initVoiceSdk', {});
  
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
133
        WidgetsBinding.instance.addObserver(this);
2eb67dd4   liangchengyou   feat:调整代码
134
      });
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
135
136
    }
  
8fb5f860   吴启风   feat:先声录音状态优化
137
138
139
140
141
    void _onVoiceXsStateChange(OnXSVoiceStateChangeEvent event,
        Emitter<TopicPictureState> emitter) async {
      emitter(XSVoiceTestState());
    }
  
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
142
    @override
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
143
144
    Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
      super.didChangeAppLifecycleState(state);
8fb5f860   吴启风   feat:先声录音状态优化
145
      Log.d('$TAG didChangeAppLifecycleState state=$state');
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
146
147
148
149
150
151
152
153
154
155
      if (state == AppLifecycleState.paused) {
        ///切到后台暂停音频播放、录音等
        if (audioPlayer.state == PlayerState.playing) {
          await audioPlayer.pause();
        }
        _voiceXsCancel();
      }
    }
  
    @override
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
156
    Future<void> close() async {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
157
      pageController.dispose();
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
158
159
160
161
      await audioPlayer.release();
      await audioPlayer.dispose();
      await _voiceXsCancel(force: true);
      await methodChannel.dispose();
d100908a   吴启风   feat:解决练习切后台后音频未暂停问题
162
      WidgetsBinding.instance.removeObserver(this);
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
163
164
165
      return super.close();
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
166
    ///请求数据
1f5969b8   biao   修复 练习和绘本播放音频问题
167
168
    void _requestData(
        RequestDataEvent event, Emitter<TopicPictureState> emitter) async {
2eb67dd4   liangchengyou   feat:调整代码
169
      try {
45c862fd   吴启风   feat:闯关交互优化-答错晃动、...
170
171
        _entity = await ListenDao.process(courseLessonId);
        emitter(RequestDataState());
2eb67dd4   liangchengyou   feat:调整代码
172
173
      } catch (e) {
        if (e is ApiException) {
1f5969b8   biao   修复 练习和绘本播放音频问题
174
          showToast(e.message ?? '请求失败,请检查网络连接');
2eb67dd4   liangchengyou   feat:调整代码
175
176
177
178
        }
      }
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
179
    ///页面切换
1f5969b8   biao   修复 练习和绘本播放音频问题
180
181
    void _pageControllerChange(CurrentPageIndexChangeEvent event,
        Emitter<TopicPictureState> emitter) async {
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
182
      await pageResetIfNeed();
8fb5f860   吴启风   feat:先声录音状态优化
183
      Log.d('$TAG _pageControllerChange $_currentPage->${event.pageIndex}');
aeafd474   吴启风   feat:选择题作答后播放音效&答...
184
185
      if (_currentPage == _entity?.topics?.length) {
        return;
3c1d5c64   liangchengyou   feat:练习功能完成
186
      }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
187
      _currentPage = event.pageIndex;
2eb67dd4   liangchengyou   feat:调整代码
188
      final topics = _entity?.topics?[_currentPage];
3c1d5c64   liangchengyou   feat:练习功能完成
189
      if (topics?.type != 3 && topics?.type != 4) {
2eb67dd4   liangchengyou   feat:调整代码
190
        if (topics?.audioUrl != null) {
1f5969b8   biao   修复 练习和绘本播放音频问题
191
          final urlStr = topics?.audioUrl ?? '';
2eb67dd4   liangchengyou   feat:调整代码
192
          if (urlStr.isNotEmpty) {
3c1d5c64   liangchengyou   feat:练习功能完成
193
194
            debugPrint(urlStr);
            await audioPlayer.play(UrlSource(urlStr));
2eb67dd4   liangchengyou   feat:调整代码
195
196
          }
        }
2eb67dd4   liangchengyou   feat:调整代码
197
      }
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
198
199
200
      emitter(CurrentPageIndexState());
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
201
    ///选择
1f5969b8   biao   修复 练习和绘本播放音频问题
202
203
    void _selectItemLoad(
        SelectItemEvent event, Emitter<TopicPictureState> emitter) async {
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
204
205
206
207
208
      _optionSelectItem = event.selectIndex;
      emitter(SelectItemChangeState());
      if (checkAnswerRight(_optionSelectItem) == true) {
        /// 如果选择题答(选)对后题目没播完,则暂停播放题目。答错的话继续播放体验也不错
        await closePlayerResource();
951eb853   吴启风   feat:代码优化-星星等动效对话...
209
210
211
212
  
        /// right音频长度比动效短,所以等动效完了再翻页
        AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.right);
        await showStarRewardDialog(context, onDismiss: () {
5989d250   吴启风   feat:体验优化-选择题选中边框...
213
          _resetSelectItem();
951eb853   吴启风   feat:代码优化-星星等动效对话...
214
215
          autoFlipPage();
        });
45c862fd   吴启风   feat:闯关交互优化-答错晃动、...
216
      } else {
951eb853   吴启风   feat:代码优化-星星等动效对话...
217
        await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.wrong);
5989d250   吴启风   feat:体验优化-选择题选中边框...
218
        _resetSelectItem();
3c1d5c64   liangchengyou   feat:练习功能完成
219
      }
7652f701   liangchengyou   feat:课程购买UI逻辑
220
    }
2eb67dd4   liangchengyou   feat:调整代码
221
  
5989d250   吴启风   feat:体验优化-选择题选中边框...
222
223
224
225
226
227
228
    ///重置选择
    void _selectItemReset(
        SelectItemResetEvent event, Emitter<TopicPictureState> emitter) async {
      ///用于一次完整的选择后重置视图
      emitter(SelectItemResetState());
    }
  
45c862fd   吴启风   feat:闯关交互优化-答错晃动、...
229
230
231
    ///为空则数据异常,用于是否晃动时需要
    bool? checkAnswerRight(int selectIndex) {
      CourseProcessTopics? topics = _entity?.topics?[_currentPage];
c623c7b2   吴启风   feat:语音跟读作答结果动效&语音
232
233
234
235
      if (topics == null ||
          topics.topicAnswerList == null ||
          selectIndex < 0 ||
          selectIndex >= topics.topicAnswerList!.length) {
45c862fd   吴启风   feat:闯关交互优化-答错晃动、...
236
237
238
239
240
241
242
        return null;
      }
      CourseProcessTopicsTopicAnswerList? answerList =
          topics.topicAnswerList?[selectIndex];
      return answerList?.correct != 0;
    }
  
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
243
    ///驰声测试
1f5969b8   biao   修复 练习和绘本播放音频问题
244
245
    void _voiceXsStart(
        XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async {
53e9e6db   吴启风   feat:绘本语音评测逻辑
246
      await audioPlayer.stop();
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
247
      // 调用封装好的权限检查和请求方法
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
248
249
      bool result = await requestPermission(
          context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
250
      if (result) {
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
251
        await methodChannel.invokeMethod('startVoice', {
1f5969b8   biao   修复 练习和绘本播放音频问题
252
253
254
255
          'word': event.testWord,
          'type': event.type,
          'userId': event.userId.toString()
        });
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
256
      }
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
257
258
    }
  
3c1d5c64   liangchengyou   feat:练习功能完成
259
    ///终止评测
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
260
    Future<void> _voiceXsStop(
1f5969b8   biao   修复 练习和绘本播放音频问题
261
        XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async {
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
262
      await methodChannel.invokeMethod('stopVoice');
3c1d5c64   liangchengyou   feat:练习功能完成
263
264
    }
  
6b0947ca   吴启风   feat:绘本增加initVoic...
265
    ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
266
267
    Future<void> _voiceXsCancel({bool force = false}) async {
      if (_isRecording || force) {
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
268
        await methodChannel.invokeMethod('cancelVoice');
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
269
      }
6b0947ca   吴启风   feat:绘本增加initVoic...
270
271
    }
  
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
272
    ///驰声评测结果
1f5969b8   biao   修复 练习和绘本播放音频问题
273
274
    void _voiceXsResult(
        XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async {
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
275
276
      _isRecording = false;
      emitter(XSVoiceTestState());
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
277
      final Map args = event.message as Map;
cb38bc90   liangchengyou   feat:视频跟读逻辑处理
278
279
      final result = args['result'] as Map;
      final overall = result['overall'].toString();
c623c7b2   吴启风   feat:语音跟读作答结果动效&语音
280
      int score = int.parse(overall);
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
281
282
      final voiceResult = VoiceResultType.fromScore(score);
      if (voiceResult.lottieFilePath != null) {
951eb853   吴启风   feat:代码优化-星星等动效对话...
283
        AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType);
0fa4e405   吴启风   feat: 将语音评测从先声迁移至驰声
284
285
        await showCheerRewardDialog(context,
            lottieFile: voiceResult.lottieFilePath!, onDismiss: () {
4f9b0fa4   吴启风   feat:语音跟读题90分以上增加...
286
          autoFlipPageByVoice(score);
951eb853   吴启风   feat:代码优化-星星等动效对话...
287
288
289
        });
      } else {
        await AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType);
4f9b0fa4   吴启风   feat:语音跟读题90分以上增加...
290
        autoFlipPageByVoice(score);
642081ad   吴启风   feat:lottie动画加载优化...
291
      }
642081ad   吴启风   feat:lottie动画加载优化...
292
293
    }
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
294
    // 暂时没用上
1f5969b8   biao   修复 练习和绘本播放音频问题
295
296
    void _voicePlayStateChange(VoicePlayStateChangeEvent event,
        Emitter<TopicPictureState> emitter) async {
3c1d5c64   liangchengyou   feat:练习功能完成
297
298
299
      emitter(VoicePlayStateChange());
    }
  
8fb5f860   吴启风   feat:先声录音状态优化
300
301
302
303
    bool isAudioPlaying() {
      return audioPlayer.state == PlayerState.playing;
    }
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
304
    // 题目音频播放
1f5969b8   biao   修复 练习和绘本播放音频问题
305
306
    void _questionVoicePlay(
        VoicePlayEvent event, Emitter<TopicPictureState> emitter) async {
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
307
      await pageResetIfNeed();
3c1d5c64   liangchengyou   feat:练习功能完成
308
      final topics = _entity?.topics?[_currentPage];
1f5969b8   biao   修复 练习和绘本播放音频问题
309
310
311
      final urlStr = topics?.audioUrl ?? '';
      await audioPlayer.play(UrlSource(urlStr),
          balance: 0.0, ctx: AudioContext());
3c1d5c64   liangchengyou   feat:练习功能完成
312
    }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
313
  
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
314
315
316
317
    /// 重置状态,音频播放、录音以及一些变量等。用于翻页,打断等场景
    Future<void> pageResetIfNeed() async {
      _optionSelectItem = -1;
      _isRecording = false;
819ae43b   吴启风   feat:体验优化-练习题目取消阻...
318
319
320
321
322
  
      await closePlayerResource();
      await _voiceXsCancel();
    }
  
5989d250   吴启风   feat:体验优化-选择题选中边框...
323
324
325
326
327
    void _resetSelectItem() {
      _optionSelectItem = -1;
      add(SelectItemResetEvent());
    }
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
328
    Future<void> closePlayerResource() async {
8fb5f860   吴启风   feat:先声录音状态优化
329
      if (isAudioPlaying()) {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
330
331
        await audioPlayer.stop();
      }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
332
333
    }
  
22f36232   吴启风   feat:过渡页-练习环节
334
335
336
337
338
    ///是否是最后一页
    bool isLastPage() {
      return currentPage == _entity?.topics?.length;
    }
  
951eb853   吴启风   feat:代码优化-星星等动效对话...
339
340
341
342
343
344
345
346
347
348
349
350
351
    ///自动翻页
    void autoFlipPage() {
      if (isLastPage()) {
        showStepPage();
      } else {
        // 答对后且播放完自动翻页
        pageController.nextPage(
          duration: const Duration(milliseconds: 250),
          curve: Curves.ease,
        );
      }
    }
  
4f9b0fa4   吴启风   feat:语音跟读题90分以上增加...
352
353
354
355
356
357
358
359
360
361
362
363
    ///语音题自动翻页
    ///90分以上自动翻页
    void autoFlipPageByVoice(int score) {
      if (score >= 90) {
        autoFlipPage();
      } else {
        if (isLastPage()) {
          showStepPage();
        }
      }
    }
  
22f36232   吴启风   feat:过渡页-练习环节
364
365
366
367
    ///展示过渡页
    void showStepPage() {
      ///如果最后一页是语音问答题,评测完后自动翻页
      sectionComplete(() {
1f5969b8   biao   修复 练习和绘本播放音频问题
368
369
370
371
372
373
        popPage(data: {
          'currentStep': currentPage,
          'courseLessonId': courseLessonId,
          'isCompleted': true,
          'nextSection': true
        });
22f36232   吴启风   feat:过渡页-练习环节
374
      }, againSectionTap: () {
22f36232   吴启风   feat:过渡页-练习环节
375
376
377
        pageController.jumpToPage(0);
      });
    }
7652f701   liangchengyou   feat:课程购买UI逻辑
378
  }