Blame view

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