Blame view

lib/pages/practice/bloc/topic_picture_bloc.dart 10.7 KB
2eb67dd4   liangchengyou   feat:调整代码
1
  import 'package:audioplayers/audioplayers.dart';
688b7b5c   liangchengyou   feat:更新文件结构
2
  import 'package:flutter/cupertino.dart';
2eb67dd4   liangchengyou   feat:调整代码
3
4
  import 'package:flutter/foundation.dart';
  import 'package:flutter/services.dart';
688b7b5c   liangchengyou   feat:更新文件结构
5
  import 'package:flutter_bloc/flutter_bloc.dart';
2eb67dd4   liangchengyou   feat:调整代码
6
  import 'package:flutter_easyloading/flutter_easyloading.dart';
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
7
  import 'package:permission_handler/permission_handler.dart';
aeafd474   吴启风   feat:选择题作答后播放音效&答...
8
  import 'package:wow_english/common/extension/string_extension.dart';
2eb67dd4   liangchengyou   feat:调整代码
9
10
11
  import 'package:wow_english/common/request/dao/listen_dao.dart';
  import 'package:wow_english/common/request/exception.dart';
  import 'package:wow_english/models/course_process_entity.dart';
22f36232   吴启风   feat:过渡页-练习环节
12
13
14
  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';
2eb67dd4   liangchengyou   feat:调整代码
15
  import 'package:wow_english/utils/loading.dart';
3c1d5c64   liangchengyou   feat:练习功能完成
16
  import 'package:wow_english/utils/toast_util.dart';
7652f701   liangchengyou   feat:课程购买UI逻辑
17
  
e811f164   吴启风   feat:权限申请页面增加隐私合规...
18
  import '../../../common/permission/permissionRequester.dart';
22f36232   吴启风   feat:过渡页-练习环节
19
  import '../../../route/route.dart';
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
20
  
7652f701   liangchengyou   feat:课程购买UI逻辑
21
  part 'topic_picture_event.dart';
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
22
  
688b7b5c   liangchengyou   feat:更新文件结构
23
  part 'topic_picture_state.dart';
7652f701   liangchengyou   feat:课程购买UI逻辑
24
  
3c1d5c64   liangchengyou   feat:练习功能完成
25
26
27
  enum VoicePlayState {
    ///未知
    unKnow,
1f5969b8   biao   修复 练习和绘本播放音频问题
28
  
3c1d5c64   liangchengyou   feat:练习功能完成
29
30
    ///播放中
    playing,
1f5969b8   biao   修复 练习和绘本播放音频问题
31
  
3c1d5c64   liangchengyou   feat:练习功能完成
32
33
    ///播放完成
    completed,
1f5969b8   biao   修复 练习和绘本播放音频问题
34
  
3c1d5c64   liangchengyou   feat:练习功能完成
35
36
37
38
    ///播放终止
    stop
  }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
39
40
  class TopicPictureBloc
      extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
41
42
    final PageController pageController;
  
608c05b4   liangchengyou   feat:兑换课程
43
    final String courseLessonId;
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
44
45
46
  
    int _currentPage = 0;
  
2eb67dd4   liangchengyou   feat:调整代码
47
48
49
50
    int _selectItem = -1;
  
    CourseProcessEntity? _entity;
  
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
51
52
    CourseProcessEntity? get entity => _entity;
  
2eb67dd4   liangchengyou   feat:调整代码
53
54
55
    ///正在评测
    bool _isVoicing = false;
  
3c1d5c64   liangchengyou   feat:练习功能完成
56
57
58
    ///正在播放音频
    VoicePlayState _voicePlayState = VoicePlayState.unKnow;
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
59
60
61
62
63
64
65
66
67
68
    // 是否是回答(选择)结果音效
    bool _isResultSoundPlaying = false;
  
    bool get isResultSoundPlaying => _isResultSoundPlaying;
  
    // 答对播放音效时禁止任何点击(选择)操作
    bool _forbiddenWhenCorrect = false;
  
    bool get forbiddenWhenCorrect => _forbiddenWhenCorrect;
  
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
69
70
71
72
    int get currentPage => _currentPage + 1;
  
    int get selectItem => _selectItem;
  
2eb67dd4   liangchengyou   feat:调整代码
73
74
    bool get isVoicing => _isVoicing;
  
1f5969b8   biao   修复 练习和绘本播放音频问题
75
    VoicePlayState get voicePlayState => _voicePlayState;
3c1d5c64   liangchengyou   feat:练习功能完成
76
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
77
    late MethodChannel methodChannel;
2eb67dd4   liangchengyou   feat:调整代码
78
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
79
    late AudioPlayer audioPlayer;
2eb67dd4   liangchengyou   feat:调整代码
80
  
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
81
82
    final BuildContext context;
  
1f5969b8   biao   修复 练习和绘本播放音频问题
83
84
    TopicPictureBloc(this.context, this.pageController, this.courseLessonId)
        : super(TopicPictureInitial()) {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
85
      on<CurrentPageIndexChangeEvent>(_pageControllerChange);
3c1d5c64   liangchengyou   feat:练习功能完成
86
87
88
      on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
      on<XSVoiceResultEvent>(_voiceXsResult);
      on<XSVoiceInitEvent>(_initVoiceSdk);
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
89
      on<SelectItemEvent>(_selectItemLoad);
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
90
      on<RequestDataEvent>(_requestData);
ae77d87f   吴启风   feat:fix语音题无法手动停止...
91
      on<XSVoiceStartEvent>(_voiceXsStart);
3c1d5c64   liangchengyou   feat:练习功能完成
92
      on<XSVoiceStopEvent>(_voiceXsStop);
aeafd474   吴启风   feat:选择题作答后播放音效&答...
93
      on<VoicePlayEvent>(_questionVoicePlay);
3c1d5c64   liangchengyou   feat:练习功能完成
94
      on<InitBlocEvent>((event, emit) {
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
95
96
        //音频播放器
        audioPlayer = AudioPlayer();
3c1d5c64   liangchengyou   feat:练习功能完成
97
        audioPlayer.onPlayerStateChanged.listen((event) async {
1f5969b8   biao   修复 练习和绘本播放音频问题
98
99
          debugPrint(
              '播放状态变化 _voicePlayState=$_voicePlayState event=$event _isResultSoundPlaying=$_isResultSoundPlaying _forbiddenWhenCorrect=$_forbiddenWhenCorrect');
aeafd474   吴启风   feat:选择题作答后播放音效&答...
100
101
102
103
104
          if (_isResultSoundPlaying) {
            if (event != PlayerState.playing) {
              _isResultSoundPlaying = false;
              if (_forbiddenWhenCorrect) {
                _forbiddenWhenCorrect = false;
77d53ec4   吴启风   feat:练习音效控制优化
105
106
                debugPrint('播放完成后解除禁止');
                if (event == PlayerState.completed) {
22f36232   吴启风   feat:过渡页-练习环节
107
108
109
110
111
                  if (isLastPage()) {
                    showStepPage();
                  } else {
                    // 答对后且播放完自动翻页
                    pageController.nextPage(
8df5fbf9   吴启风   feat:单元列表页由于ios嵌套...
112
                      duration: const Duration(milliseconds: 250),
22f36232   吴启风   feat:过渡页-练习环节
113
114
115
                      curve: Curves.ease,
                    );
                  }
77d53ec4   吴启风   feat:练习音效控制优化
116
                }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
117
118
119
120
121
122
123
124
125
126
127
              }
            }
          } else {
            if (event == PlayerState.completed) {
              debugPrint('播放完成');
              _voicePlayState = VoicePlayState.completed;
            }
            if (event == PlayerState.stopped) {
              debugPrint('播放结束');
              _voicePlayState = VoicePlayState.stop;
            }
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
128
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
129
130
131
132
133
134
135
136
            if (event == PlayerState.playing) {
              debugPrint('正在播放中');
              _voicePlayState = VoicePlayState.playing;
            }
            if (isClosed) {
              return;
            }
            add(VoicePlayStateChangeEvent());
3c1d5c64   liangchengyou   feat:练习功能完成
137
          }
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
138
139
        });
  
1f5969b8   biao   修复 练习和绘本播放音频问题
140
141
        methodChannel =
            const MethodChannel('wow_english/sing_sound_method_channel');
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
142
        methodChannel.setMethodCallHandler((call) async {
1f5969b8   biao   修复 练习和绘本播放音频问题
143
144
145
146
          if (call.method == 'voiceResult') {
            //评测结果
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
147
            add(XSVoiceResultEvent(call.arguments));
b90a1518   liangchengyou   feat:练习接口逻辑完成
148
149
150
            return;
          }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
151
152
          if (call.method == 'voiceStart') {
            //评测开始
b90a1518   liangchengyou   feat:练习接口逻辑完成
153
154
155
156
157
158
            if (kDebugMode) {
              print('评测开始');
            }
            return;
          }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
159
160
161
162
          if (call.method == 'voiceEnd') {
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
            //评测结束
b90a1518   liangchengyou   feat:练习接口逻辑完成
163
164
165
166
167
168
            if (kDebugMode) {
              print('评测结束');
            }
            return;
          }
  
1f5969b8   biao   修复 练习和绘本播放音频问题
169
170
171
172
173
          if (call.method == 'voiceFail') {
            //评测失败
            await audioPlayer.setAudioContext(AudioContext());
            await audioPlayer.setBalance(0.0);
  
b90a1518   liangchengyou   feat:练习接口逻辑完成
174
175
            EasyLoading.showToast('评测失败');
            return;
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
176
177
          }
        });
2eb67dd4   liangchengyou   feat:调整代码
178
      });
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
179
180
181
    }
  
    @override
1f5969b8   biao   修复 练习和绘本播放音频问题
182
    Future<void> close() {
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
183
      pageController.dispose();
3c1d5c64   liangchengyou   feat:练习功能完成
184
      audioPlayer.release();
2eb67dd4   liangchengyou   feat:调整代码
185
      audioPlayer.dispose();
aeafd474   吴启风   feat:选择题作答后播放音效&答...
186
187
      _isResultSoundPlaying = false;
      _forbiddenWhenCorrect = false;
6b0947ca   吴启风   feat:绘本增加initVoic...
188
      _voiceXsCancel();
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
189
190
191
      return super.close();
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
192
    ///请求数据
1f5969b8   biao   修复 练习和绘本播放音频问题
193
194
    void _requestData(
        RequestDataEvent event, Emitter<TopicPictureState> emitter) async {
2eb67dd4   liangchengyou   feat:调整代码
195
196
      try {
        await loading(() async {
608c05b4   liangchengyou   feat:兑换课程
197
          _entity = await ListenDao.process(courseLessonId);
2eb67dd4   liangchengyou   feat:调整代码
198
199
200
201
          emitter(RequestDataState());
        });
      } catch (e) {
        if (e is ApiException) {
1f5969b8   biao   修复 练习和绘本播放音频问题
202
          showToast(e.message ?? '请求失败,请检查网络连接');
2eb67dd4   liangchengyou   feat:调整代码
203
204
205
206
        }
      }
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
207
    ///页面切换
1f5969b8   biao   修复 练习和绘本播放音频问题
208
209
    void _pageControllerChange(CurrentPageIndexChangeEvent event,
        Emitter<TopicPictureState> emitter) async {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
210
211
212
213
      await closePlayerResource();
      debugPrint('翻页 $_currentPage->${event.pageIndex}');
      if (_currentPage == _entity?.topics?.length) {
        return;
3c1d5c64   liangchengyou   feat:练习功能完成
214
      }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
215
      _currentPage = event.pageIndex;
2eb67dd4   liangchengyou   feat:调整代码
216
      final topics = _entity?.topics?[_currentPage];
3c1d5c64   liangchengyou   feat:练习功能完成
217
      if (topics?.type != 3 && topics?.type != 4) {
2eb67dd4   liangchengyou   feat:调整代码
218
        if (topics?.audioUrl != null) {
1f5969b8   biao   修复 练习和绘本播放音频问题
219
          final urlStr = topics?.audioUrl ?? '';
2eb67dd4   liangchengyou   feat:调整代码
220
          if (urlStr.isNotEmpty) {
3c1d5c64   liangchengyou   feat:练习功能完成
221
222
            debugPrint(urlStr);
            await audioPlayer.play(UrlSource(urlStr));
2eb67dd4   liangchengyou   feat:调整代码
223
224
          }
        }
2eb67dd4   liangchengyou   feat:调整代码
225
      }
b90a1518   liangchengyou   feat:练习接口逻辑完成
226
      _selectItem = -1;
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
227
228
229
      emitter(CurrentPageIndexState());
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
230
    ///选择
1f5969b8   biao   修复 练习和绘本播放音频问题
231
232
    void _selectItemLoad(
        SelectItemEvent event, Emitter<TopicPictureState> emitter) async {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
233
234
235
      if (_forbiddenWhenCorrect) {
        return;
      }
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
236
      _selectItem = event.selectIndex;
3c1d5c64   liangchengyou   feat:练习功能完成
237
      CourseProcessTopics? topics = _entity?.topics?[_currentPage];
1f5969b8   biao   修复 练习和绘本播放音频问题
238
239
      CourseProcessTopicsTopicAnswerList? answerList =
          topics?.topicAnswerList?[_selectItem];
3c1d5c64   liangchengyou   feat:练习功能完成
240
      if (answerList?.correct == 0) {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
241
242
        _playResultSound(false);
        // showToast('继续加油哦',duration: const Duration(seconds: 2));
3c1d5c64   liangchengyou   feat:练习功能完成
243
      } else {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
244
245
        _playResultSound(true);
        // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2));
3c1d5c64   liangchengyou   feat:练习功能完成
246
      }
624214d0   liangchengyou   feat:看题选字/选图UI和部分逻辑
247
      emitter(SelectItemChangeState());
7652f701   liangchengyou   feat:课程购买UI逻辑
248
    }
2eb67dd4   liangchengyou   feat:调整代码
249
  
3c1d5c64   liangchengyou   feat:练习功能完成
250
    ///初始化SDK
1f5969b8   biao   修复 练习和绘本播放音频问题
251
252
253
    _initVoiceSdk(
        XSVoiceInitEvent event, Emitter<TopicPictureState> emitter) async {
      methodChannel.invokeMethod('initVoiceSdk', event.data);
3c1d5c64   liangchengyou   feat:练习功能完成
254
255
    }
  
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
256
    ///先声测试
1f5969b8   biao   修复 练习和绘本播放音频问题
257
258
    void _voiceXsStart(
        XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async {
53e9e6db   吴启风   feat:绘本语音评测逻辑
259
      await audioPlayer.stop();
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
260
      // 调用封装好的权限检查和请求方法
1e22b7d1   吴启风   feat:儿歌/视频环节接口请求时机优化
261
262
      bool result = await requestPermission(
          context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
263
      if (result) {
1f5969b8   biao   修复 练习和绘本播放音频问题
264
265
266
267
268
        methodChannel.invokeMethod('startVoice', {
          'word': event.testWord,
          'type': event.type,
          'userId': event.userId.toString()
        });
354ac7e6   吴启风   feat:隐藏视频跟读入口、底部草...
269
270
271
        _isVoicing = true;
        emitter(XSVoiceTestState());
      }
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
272
273
    }
  
3c1d5c64   liangchengyou   feat:练习功能完成
274
    ///终止评测
1f5969b8   biao   修复 练习和绘本播放音频问题
275
276
    void _voiceXsStop(
        XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async {
3c1d5c64   liangchengyou   feat:练习功能完成
277
278
279
      methodChannel.invokeMethod('stopVoice');
    }
  
6b0947ca   吴启风   feat:绘本增加initVoic...
280
281
282
283
284
    ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
    void _voiceXsCancel() {
      methodChannel.invokeMethod('cancelVoice');
    }
  
3c1d5c64   liangchengyou   feat:练习功能完成
285
    ///先声评测结果
1f5969b8   biao   修复 练习和绘本播放音频问题
286
287
    void _voiceXsResult(
        XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async {
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
288
      final Map args = event.message as Map;
cb38bc90   liangchengyou   feat:视频跟读逻辑处理
289
290
      final result = args['result'] as Map;
      final overall = result['overall'].toString();
1f5969b8   biao   修复 练习和绘本播放音频问题
291
      showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5));
2eb67dd4   liangchengyou   feat:调整代码
292
      _isVoicing = false;
e3c2820c   liangchengyou   feat:先声SDK逻辑调整
293
      emitter(XSVoiceTestState());
22f36232   吴启风   feat:过渡页-练习环节
294
295
296
      if (isLastPage()) {
        showStepPage();
      }
2eb67dd4   liangchengyou   feat:调整代码
297
    }
3c1d5c64   liangchengyou   feat:练习功能完成
298
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
299
    // 暂时没用上
1f5969b8   biao   修复 练习和绘本播放音频问题
300
301
    void _voicePlayStateChange(VoicePlayStateChangeEvent event,
        Emitter<TopicPictureState> emitter) async {
3c1d5c64   liangchengyou   feat:练习功能完成
302
303
304
      emitter(VoicePlayStateChange());
    }
  
aeafd474   吴启风   feat:选择题作答后播放音效&答...
305
    // 题目音频播放
1f5969b8   biao   修复 练习和绘本播放音频问题
306
307
    void _questionVoicePlay(
        VoicePlayEvent event, Emitter<TopicPictureState> emitter) async {
aeafd474   吴启风   feat:选择题作答后播放音效&答...
308
      if (_forbiddenWhenCorrect) {
3c1d5c64   liangchengyou   feat:练习功能完成
309
310
        return;
      }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
311
312
      _forbiddenWhenCorrect = false;
      await closePlayerResource();
3c1d5c64   liangchengyou   feat:练习功能完成
313
      final topics = _entity?.topics?[_currentPage];
1f5969b8   biao   修复 练习和绘本播放音频问题
314
315
316
      final urlStr = topics?.audioUrl ?? '';
      await audioPlayer.play(UrlSource(urlStr),
          balance: 0.0, ctx: AudioContext());
3c1d5c64   liangchengyou   feat:练习功能完成
317
    }
aeafd474   吴启风   feat:选择题作答后播放音效&答...
318
319
320
321
322
323
324
  
    Future<void> closePlayerResource() async {
      if (voicePlayState == VoicePlayState.playing || _isResultSoundPlaying) {
        await audioPlayer.stop();
      }
    }
  
22f36232   吴启风   feat:过渡页-练习环节
325
    ///播放选择结果音效
aeafd474   吴启风   feat:选择题作答后播放音效&答...
326
    void _playResultSound(bool isCorrect) async {
77d53ec4   吴启风   feat:练习音效控制优化
327
      // await audioPlayer.stop();
1f5969b8   biao   修复 练习和绘本播放音频问题
328
329
      if (audioPlayer.state == PlayerState.playing &&
          _isResultSoundPlaying == false) {
77d53ec4   吴启风   feat:练习音效控制优化
330
331
332
        _voicePlayState = VoicePlayState.stop;
      }
      debugPrint("_playResultSound isCorrect=$isCorrect");
aeafd474   吴启风   feat:选择题作答后播放音效&答...
333
334
335
336
337
338
339
340
      _isResultSoundPlaying = true;
      _forbiddenWhenCorrect = isCorrect;
      if (isCorrect) {
        await audioPlayer.play(AssetSource('correct_voice'.assetMp3));
      } else {
        await audioPlayer.play(AssetSource('incorrect_voice'.assetMp3));
      }
    }
22f36232   吴启风   feat:过渡页-练习环节
341
342
343
344
345
346
347
348
349
350
  
    ///是否是最后一页
    bool isLastPage() {
      return currentPage == _entity?.topics?.length;
    }
  
    ///展示过渡页
    void showStepPage() {
      ///如果最后一页是语音问答题,评测完后自动翻页
      sectionComplete(() {
1f5969b8   biao   修复 练习和绘本播放音频问题
351
352
353
354
355
356
        popPage(data: {
          'currentStep': currentPage,
          'courseLessonId': courseLessonId,
          'isCompleted': true,
          'nextSection': true
        });
22f36232   吴启风   feat:过渡页-练习环节
357
      }, againSectionTap: () {
22f36232   吴启风   feat:过渡页-练习环节
358
359
360
        pageController.jumpToPage(0);
      });
    }
7652f701   liangchengyou   feat:课程购买UI逻辑
361
  }