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