Commit 819ae43b6372c182f6ab91a6313e226000746817

Authored by 吴启风
1 parent 081fbff7

feat:体验优化-练习题目取消阻塞,支持任何条件下的点击,增加体验流畅感

lib/common/utils/click_with_music_controller.dart
@@ -11,6 +11,8 @@ class ClickWithMusicController { @@ -11,6 +11,8 @@ class ClickWithMusicController {
11 11
12 static ClickWithMusicController? _instance; 12 static ClickWithMusicController? _instance;
13 13
  14 + static String TAG = 'ClickWithMusicController';
  15 +
14 ClickWithMusicController._privateConstructor(); 16 ClickWithMusicController._privateConstructor();
15 17
16 static ClickWithMusicController get instance => _instance ??= ClickWithMusicController._privateConstructor(); 18 static ClickWithMusicController get instance => _instance ??= ClickWithMusicController._privateConstructor();
@@ -21,11 +23,14 @@ class ClickWithMusicController { @@ -21,11 +23,14 @@ class ClickWithMusicController {
21 ///@param action 可以是同步函数也可以是异步函数 23 ///@param action 可以是同步函数也可以是异步函数
22 Future<void> playMusicAndPerformAction(BuildContext? context, 24 Future<void> playMusicAndPerformAction(BuildContext? context,
23 AudioPlayerUtilType audioType, FutureOr<void> Function() action) async { 25 AudioPlayerUtilType audioType, FutureOr<void> Function() action) async {
  26 + Log.d("$TAG playMusicAndPerformAction _isPlaying=$_isPlaying");
  27 + ///todo 是否需要考虑打断覆盖能力
24 if (_isPlaying) return; 28 if (_isPlaying) return;
25 29
26 _isPlaying = true; 30 _isPlaying = true;
27 - Log.d("WQF playMusicAndPerformAction playAudio begin"); 31 + Log.d("$TAG playMusicAndPerformAction playAudio begin");
28 32
  33 + await AudioPlayerUtil.getInstance().pause();
29 // Play the music 34 // Play the music
30 await AudioPlayerUtil.getInstance() 35 await AudioPlayerUtil.getInstance()
31 .playAudio(audioType); 36 .playAudio(audioType);
@@ -33,15 +38,16 @@ class ClickWithMusicController { @@ -33,15 +38,16 @@ class ClickWithMusicController {
33 try { 38 try {
34 await Future.sync(action); 39 await Future.sync(action);
35 } catch (e) { 40 } catch (e) {
36 - Log.d('WQF playMusicAndPerformAction exception $e'); 41 + Log.d('$TAG playMusicAndPerformAction exception $e');
37 } finally { 42 } finally {
38 - Log.d("WQF playMusicAndPerformAction playAudio end"); 43 + Log.d("$TAG playMusicAndPerformAction playAudio end");
39 _isPlaying = false; 44 _isPlaying = false;
40 } 45 }
41 } 46 }
42 47
43 - void reset() { 48 + Future<void> reset() async {
44 _isPlaying = false; 49 _isPlaying = false;
  50 + await AudioPlayerUtil.getInstance().stop();
45 } 51 }
46 52
47 // void dispose() { 53 // void dispose() {
lib/common/widgets/cheer_reward_widget.dart
@@ -54,13 +54,13 @@ class _CheerRewardWidgetState extends State&lt;CheerRewardWidget&gt; @@ -54,13 +54,13 @@ class _CheerRewardWidgetState extends State&lt;CheerRewardWidget&gt;
54 } 54 }
55 55
56 void _startAnimation() { 56 void _startAnimation() {
57 - Log.d("$TAG _startAnimation"); 57 + Log.d("$TAG ${identityHashCode(this)} _startAnimation");
58 setState(() { 58 setState(() {
59 _isVisible = true; 59 _isVisible = true;
60 }); 60 });
61 61
62 _futureComposition.then((composition) { 62 _futureComposition.then((composition) {
63 - Log.d("$TAG _futureComposition.then duration=${composition.duration}"); 63 + Log.d("$TAG ${identityHashCode(this)} _futureComposition.then duration=${composition.duration}");
64 _controller.duration = composition.duration; 64 _controller.duration = composition.duration;
65 _controller.forward().whenCompleteOrCancel(() { 65 _controller.forward().whenCompleteOrCancel(() {
66 if (mounted) { 66 if (mounted) {
lib/common/widgets/recorder_widget.dart
@@ -14,7 +14,7 @@ class RecorderWidget extends StatefulWidget { @@ -14,7 +14,7 @@ class RecorderWidget extends StatefulWidget {
14 14
15 const RecorderWidget({ 15 const RecorderWidget({
16 Key? key, 16 Key? key,
17 - required this.isClickable, 17 + this.isClickable = true,
18 required this.isPlaying, 18 required this.isPlaying,
19 required this.onTap, 19 required this.onTap,
20 required this.width, 20 required this.width,
lib/common/widgets/speaker_widget.dart
@@ -52,7 +52,10 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt; @@ -52,7 +52,10 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
52 Log.d( 52 Log.d(
53 "$TAG ${identityHashCode(this)} initState widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller=${identityHashCode(_controller)}"); 53 "$TAG ${identityHashCode(this)} initState widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller=${identityHashCode(_controller)}");
54 if (widget.isPlaying) { 54 if (widget.isPlaying) {
55 - _startAnimation(); 55 + ///fixme 增加200毫秒延迟,避免一进入就已经开始播了,效果有待观察
  56 + Future.delayed(const Duration(milliseconds: 200), () {
  57 + _startAnimation();
  58 + });
56 } 59 }
57 } 60 }
58 61
lib/models/voice_result_type.dart 0 → 100644
  1 +import 'package:wow_english/utils/audio_player_util.dart';
  2 +
  3 +/// 语音评测结果聚合类
  4 +class VoiceResultType {
  5 + /// lottie动画文件路径
  6 + final String? lottieFilePath;
  7 + /// 音效
  8 + final AudioPlayerUtilType audioType;
  9 + /// 得分范围最小值
  10 + final int minScore;
  11 + /// 得分范围最大值
  12 + final int maxScore;
  13 +
  14 + const VoiceResultType._(this.lottieFilePath, this.audioType, this.minScore, this.maxScore);
  15 +
  16 + static const VoiceResultType level1 = VoiceResultType._('assets/lotties/excellent.zip', AudioPlayerUtilType.excellent, 90, 100);
  17 + static const VoiceResultType level2 = VoiceResultType._('assets/lotties/great.zip', AudioPlayerUtilType.great, 70, 89);
  18 + static const VoiceResultType level3 = VoiceResultType._('assets/lotties/good.zip', AudioPlayerUtilType.good, 50, 69);
  19 + static const VoiceResultType level4 = VoiceResultType._(null, AudioPlayerUtilType.tryAgain, 0, 49);
  20 +
  21 + static List<VoiceResultType> get values => [level1, level2, level3, level4];
  22 +
  23 + static VoiceResultType fromScore(int score) {
  24 + return values.firstWhere(
  25 + (type) => score >= type.minScore && score <= type.maxScore,
  26 + orElse: () => level4, // 默认返回level4
  27 + );
  28 + }
  29 +}
0 \ No newline at end of file 30 \ No newline at end of file
lib/pages/practice/bloc/topic_picture_bloc.dart
@@ -20,8 +20,8 @@ import &#39;package:wow_english/utils/toast_util.dart&#39;; @@ -20,8 +20,8 @@ import &#39;package:wow_english/utils/toast_util.dart&#39;;
20 import '../../../common/permission/permissionRequester.dart'; 20 import '../../../common/permission/permissionRequester.dart';
21 import '../../../common/utils/click_with_music_controller.dart'; 21 import '../../../common/utils/click_with_music_controller.dart';
22 import '../../../common/utils/show_star_reward_dialog.dart'; 22 import '../../../common/utils/show_star_reward_dialog.dart';
  23 +import '../../../models/voice_result_type.dart';
23 import '../../../route/route.dart'; 24 import '../../../route/route.dart';
24 -import '../../../utils/log_util.dart';  
25 25
26 part 'topic_picture_event.dart'; 26 part 'topic_picture_event.dart';
27 27
@@ -49,7 +49,7 @@ class TopicPictureBloc @@ -49,7 +49,7 @@ class TopicPictureBloc
49 49
50 int _currentPage = 0; 50 int _currentPage = 0;
51 51
52 - int _selectItem = -1; 52 + int _optionSelectItem = -1;
53 53
54 CourseProcessEntity? _entity; 54 CourseProcessEntity? _entity;
55 55
@@ -61,19 +61,10 @@ class TopicPictureBloc @@ -61,19 +61,10 @@ class TopicPictureBloc
61 ///正在播放音频 61 ///正在播放音频
62 VoicePlayState _voicePlayState = VoicePlayState.unKnow; 62 VoicePlayState _voicePlayState = VoicePlayState.unKnow;
63 63
64 - // 是否是回答(选择)结果音效  
65 - bool _isResultSoundPlaying = false;  
66 -  
67 - bool get isResultSoundPlaying => _isResultSoundPlaying;  
68 -  
69 - // 答对播放音效时禁止任何点击(选择)操作  
70 - bool _forbiddenWhenCorrect = false;  
71 -  
72 - bool get forbiddenWhenCorrect => _forbiddenWhenCorrect;  
73 -  
74 int get currentPage => _currentPage + 1; 64 int get currentPage => _currentPage + 1;
75 65
76 - int get selectItem => _selectItem; 66 + /// 选择题选中项
  67 + int get optionSelectItem => _optionSelectItem;
77 68
78 bool get isRecording => _isRecording; 69 bool get isRecording => _isRecording;
79 70
@@ -103,46 +94,24 @@ class TopicPictureBloc @@ -103,46 +94,24 @@ class TopicPictureBloc
103 //音频播放器 94 //音频播放器
104 audioPlayer = AudioPlayer(); 95 audioPlayer = AudioPlayer();
105 audioPlayer.onPlayerStateChanged.listen((event) async { 96 audioPlayer.onPlayerStateChanged.listen((event) async {
106 - debugPrint(  
107 - '播放状态变化 _voicePlayState=$_voicePlayState event=$event _isResultSoundPlaying=$_isResultSoundPlaying _forbiddenWhenCorrect=$_forbiddenWhenCorrect');  
108 - if (_isResultSoundPlaying) {  
109 - if (event != PlayerState.playing) {  
110 - _isResultSoundPlaying = false;  
111 - if (_forbiddenWhenCorrect) {  
112 - _forbiddenWhenCorrect = false;  
113 - debugPrint('播放完成后解除禁止');  
114 - if (event == PlayerState.completed) {  
115 - if (isLastPage()) {  
116 - showStepPage();  
117 - } else {  
118 - // 答对后且播放完自动翻页  
119 - pageController.nextPage(  
120 - duration: const Duration(milliseconds: 250),  
121 - curve: Curves.ease,  
122 - );  
123 - }  
124 - }  
125 - }  
126 - }  
127 - } else {  
128 - if (event == PlayerState.completed) {  
129 - debugPrint('播放完成');  
130 - _voicePlayState = VoicePlayState.completed;  
131 - }  
132 - if (event == PlayerState.stopped) {  
133 - debugPrint('播放结束');  
134 - _voicePlayState = VoicePlayState.stop;  
135 - } 97 + debugPrint('播放状态变化 _voicePlayState=$_voicePlayState event=$event');
  98 + if (event == PlayerState.completed) {
  99 + debugPrint('播放完成');
  100 + _voicePlayState = VoicePlayState.completed;
  101 + }
  102 + if (event == PlayerState.stopped) {
  103 + debugPrint('播放结束');
  104 + _voicePlayState = VoicePlayState.stop;
  105 + }
136 106
137 - if (event == PlayerState.playing) {  
138 - debugPrint('正在播放中');  
139 - _voicePlayState = VoicePlayState.playing;  
140 - }  
141 - if (isClosed) {  
142 - return;  
143 - }  
144 - add(VoicePlayStateChangeEvent()); 107 + if (event == PlayerState.playing) {
  108 + debugPrint('正在播放中');
  109 + _voicePlayState = VoicePlayState.playing;
145 } 110 }
  111 + if (isClosed) {
  112 + return;
  113 + }
  114 + add(VoicePlayStateChangeEvent());
146 }); 115 });
147 116
148 methodChannel = 117 methodChannel =
@@ -191,8 +160,6 @@ class TopicPictureBloc @@ -191,8 +160,6 @@ class TopicPictureBloc
191 pageController.dispose(); 160 pageController.dispose();
192 audioPlayer.release(); 161 audioPlayer.release();
193 audioPlayer.dispose(); 162 audioPlayer.dispose();
194 - _isResultSoundPlaying = false;  
195 - _forbiddenWhenCorrect = false;  
196 _voiceXsCancel(); 163 _voiceXsCancel();
197 return super.close(); 164 return super.close();
198 } 165 }
@@ -213,7 +180,7 @@ class TopicPictureBloc @@ -213,7 +180,7 @@ class TopicPictureBloc
213 ///页面切换 180 ///页面切换
214 void _pageControllerChange(CurrentPageIndexChangeEvent event, 181 void _pageControllerChange(CurrentPageIndexChangeEvent event,
215 Emitter<TopicPictureState> emitter) async { 182 Emitter<TopicPictureState> emitter) async {
216 - await closePlayerResource(); 183 + await pageResetIfNeed();
217 debugPrint('翻页 $_currentPage->${event.pageIndex}'); 184 debugPrint('翻页 $_currentPage->${event.pageIndex}');
218 if (_currentPage == _entity?.topics?.length) { 185 if (_currentPage == _entity?.topics?.length) {
219 return; 186 return;
@@ -229,26 +196,22 @@ class TopicPictureBloc @@ -229,26 +196,22 @@ class TopicPictureBloc
229 } 196 }
230 } 197 }
231 } 198 }
232 - _selectItem = -1;  
233 emitter(CurrentPageIndexState()); 199 emitter(CurrentPageIndexState());
234 } 200 }
235 201
236 ///选择 202 ///选择
237 void _selectItemLoad( 203 void _selectItemLoad(
238 SelectItemEvent event, Emitter<TopicPictureState> emitter) async { 204 SelectItemEvent event, Emitter<TopicPictureState> emitter) async {
239 - if (_forbiddenWhenCorrect) {  
240 - return;  
241 - }  
242 - _selectItem = event.selectIndex;  
243 - if (checkAnswerRight(_selectItem) == true) {  
244 - _playResultSound(true); 205 + _optionSelectItem = event.selectIndex;
  206 + emitter(SelectItemChangeState());
  207 + if (checkAnswerRight(_optionSelectItem) == true) {
  208 + /// 如果选择题答(选)对后题目没播完,则暂停播放题目。答错的话继续播放体验也不错
  209 + await closePlayerResource();
245 showStarRewardDialog(context); 210 showStarRewardDialog(context);
246 - // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); 211 + await _playResultSound(true);
247 } else { 212 } else {
248 - _playResultSound(false);  
249 - // showToast('继续加油哦',duration: const Duration(seconds: 2)); 213 + await _playResultSound(false);
250 } 214 }
251 - emitter(SelectItemChangeState());  
252 } 215 }
253 216
254 ///为空则数据异常,用于是否晃动时需要 217 ///为空则数据异常,用于是否晃动时需要
@@ -290,69 +253,37 @@ class TopicPictureBloc @@ -290,69 +253,37 @@ class TopicPictureBloc
290 } 253 }
291 254
292 ///终止评测 255 ///终止评测
293 - void _voiceXsStop( 256 + Future<void> _voiceXsStop(
294 XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async { 257 XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async {
295 methodChannel.invokeMethod('stopVoice'); 258 methodChannel.invokeMethod('stopVoice');
296 } 259 }
297 260
298 ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) 261 ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
299 - void _voiceXsCancel() {  
300 - methodChannel.invokeMethod('cancelVoice'); 262 + Future<void> _voiceXsCancel({bool force = false}) async {
  263 + if (_isRecording || force) {
  264 + methodChannel.invokeMethod('cancelVoice');
  265 + }
301 } 266 }
302 267
303 ///先声评测结果 268 ///先声评测结果
304 void _voiceXsResult( 269 void _voiceXsResult(
305 XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async { 270 XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async {
  271 + _isRecording = false;
  272 + emitter(XSVoiceTestState());
306 final Map args = event.message as Map; 273 final Map args = event.message as Map;
307 final result = args['result'] as Map; 274 final result = args['result'] as Map;
308 final overall = result['overall'].toString(); 275 final overall = result['overall'].toString();
309 int score = int.parse(overall); 276 int score = int.parse(overall);
310 - AudioPlayerUtilType audioPlayerUtilType;  
311 - String? lottieFile;  
312 - if (score > 90) {  
313 - audioPlayerUtilType = AudioPlayerUtilType.excellent;  
314 - lottieFile = 'assets/lotties/excellent.zip';  
315 - } else if (score > 70) {  
316 - audioPlayerUtilType = AudioPlayerUtilType.great;  
317 - lottieFile = 'assets/lotties/great.zip';  
318 - } else if (score > 50) {  
319 - audioPlayerUtilType = AudioPlayerUtilType.good;  
320 - lottieFile = 'assets/lotties/good.zip';  
321 - } else {  
322 - audioPlayerUtilType = AudioPlayerUtilType.tryAgain;  
323 - }  
324 - if (lottieFile != null) {  
325 - showCheerRewardDialog(context, lottieFile: lottieFile);  
326 - }  
327 - Log.d("WQF audioPlayerUtilType=$audioPlayerUtilType lottieFile=$lottieFile");  
328 - ClickWithMusicController.instance  
329 - .playMusicAndPerformAction(context, audioPlayerUtilType, () => {  
330 - ///todo 是否需要自动翻页  
331 - });  
332 - // showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5));  
333 - _isRecording = false;  
334 - emitter(XSVoiceTestState());  
335 - if (isLastPage()) {  
336 - showStepPage();  
337 - }  
338 - }  
339 -  
340 - /// 根据得分计算星星数  
341 - int _evaluateScore(String scoreStr) {  
342 - try {  
343 - int score = int.parse(scoreStr);  
344 - if (score > 80) {  
345 - return 3;  
346 - } else if (score > 60) {  
347 - return 2;  
348 - } else {  
349 - return 1;  
350 - }  
351 - } catch (e) {  
352 - // 如果转换失败,可以返回一个默认值或抛出异常  
353 - print('Error parsing score: $e');  
354 - return 1; // 返回一个默认值表示错误 277 + final voiceResult = VoiceResultType.fromScore(score);
  278 + if (voiceResult.lottieFilePath != null) {
  279 + showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!);
355 } 280 }
  281 + await ClickWithMusicController.instance.playMusicAndPerformAction(
  282 + context,
  283 + voiceResult.audioType,
  284 + () {
  285 + if (isLastPage()) {showStepPage();};
  286 + });
356 } 287 }
357 288
358 // 暂时没用上 289 // 暂时没用上
@@ -364,38 +295,46 @@ class TopicPictureBloc @@ -364,38 +295,46 @@ class TopicPictureBloc
364 // 题目音频播放 295 // 题目音频播放
365 void _questionVoicePlay( 296 void _questionVoicePlay(
366 VoicePlayEvent event, Emitter<TopicPictureState> emitter) async { 297 VoicePlayEvent event, Emitter<TopicPictureState> emitter) async {
367 - if (_forbiddenWhenCorrect) {  
368 - return;  
369 - }  
370 - _forbiddenWhenCorrect = false;  
371 - await closePlayerResource(); 298 + await pageResetIfNeed();
372 final topics = _entity?.topics?[_currentPage]; 299 final topics = _entity?.topics?[_currentPage];
373 final urlStr = topics?.audioUrl ?? ''; 300 final urlStr = topics?.audioUrl ?? '';
374 await audioPlayer.play(UrlSource(urlStr), 301 await audioPlayer.play(UrlSource(urlStr),
375 balance: 0.0, ctx: AudioContext()); 302 balance: 0.0, ctx: AudioContext());
376 } 303 }
377 304
  305 + /// 重置状态,音频播放、录音以及一些变量等。用于翻页,打断等场景
  306 + Future<void> pageResetIfNeed() async {
  307 + _optionSelectItem = -1;
  308 + _isRecording = false;
  309 + _voicePlayState = VoicePlayState.stop;
  310 +
  311 + await closePlayerResource();
  312 + await _voiceXsCancel();
  313 + }
  314 +
378 Future<void> closePlayerResource() async { 315 Future<void> closePlayerResource() async {
379 - if (voicePlayState == VoicePlayState.playing || _isResultSoundPlaying) { 316 + if (voicePlayState == VoicePlayState.playing) {
380 await audioPlayer.stop(); 317 await audioPlayer.stop();
381 } 318 }
  319 + await ClickWithMusicController.instance.reset();
382 } 320 }
383 321
384 ///播放选择结果音效 322 ///播放选择结果音效
385 - void _playResultSound(bool isCorrect) async {  
386 - // await audioPlayer.stop();  
387 - if (audioPlayer.state == PlayerState.playing &&  
388 - _isResultSoundPlaying == false) {  
389 - _voicePlayState = VoicePlayState.stop;  
390 - }  
391 - debugPrint("_playResultSound isCorrect=$isCorrect");  
392 - _isResultSoundPlaying = true;  
393 - _forbiddenWhenCorrect = isCorrect;  
394 - if (isCorrect) {  
395 - await audioPlayer.play(AssetSource('right'.assetMp3));  
396 - } else {  
397 - await audioPlayer.play(AssetSource('wrong'.assetMp3));  
398 - } 323 + Future<void> _playResultSound(bool isCorrect) async {
  324 + await ClickWithMusicController.instance.playMusicAndPerformAction(context,
  325 + isCorrect ? AudioPlayerUtilType.right : AudioPlayerUtilType.wrong, () {
  326 + if (isCorrect) {
  327 + if (isLastPage()) {
  328 + showStepPage();
  329 + } else {
  330 + // 答对后且播放完自动翻页
  331 + pageController.nextPage(
  332 + duration: const Duration(milliseconds: 250),
  333 + curve: Curves.ease,
  334 + );
  335 + }
  336 + }
  337 + });
399 } 338 }
400 339
401 ///是否是最后一页 340 ///是否是最后一页
lib/pages/practice/topic_picture_page.dart
@@ -173,7 +173,7 @@ class _TopicPicturePage extends StatelessWidget { @@ -173,7 +173,7 @@ class _TopicPicturePage extends StatelessWidget {
173 buildWhen: (_, s) => s is SelectItemChangeState, 173 buildWhen: (_, s) => s is SelectItemChangeState,
174 builder: (context, state) { 174 builder: (context, state) {
175 final bloc = BlocProvider.of<TopicPictureBloc>(context); 175 final bloc = BlocProvider.of<TopicPictureBloc>(context);
176 - final isAnswerOption = bloc.selectItem == index; 176 + final isAnswerOption = bloc.optionSelectItem == index;
177 final answerCorrect = 177 final answerCorrect =
178 isAnswerOption && bloc.checkAnswerRight(index) == true; 178 isAnswerOption && bloc.checkAnswerRight(index) == true;
179 return Container( 179 return Container(
@@ -243,7 +243,7 @@ class _TopicPicturePage extends StatelessWidget { @@ -243,7 +243,7 @@ class _TopicPicturePage extends StatelessWidget {
243 buildWhen: (_, s) => s is SelectItemChangeState, 243 buildWhen: (_, s) => s is SelectItemChangeState,
244 builder: (context, state) { 244 builder: (context, state) {
245 final bloc = BlocProvider.of<TopicPictureBloc>(context); 245 final bloc = BlocProvider.of<TopicPictureBloc>(context);
246 - final isAnswerOption = bloc.selectItem == index; 246 + final isAnswerOption = bloc.optionSelectItem == index;
247 final answerCorrect = 247 final answerCorrect =
248 isAnswerOption && bloc.checkAnswerRight(index) == true; 248 isAnswerOption && bloc.checkAnswerRight(index) == true;
249 return Container( 249 return Container(
@@ -349,7 +349,7 @@ class _TopicPicturePage extends StatelessWidget { @@ -349,7 +349,7 @@ class _TopicPicturePage extends StatelessWidget {
349 buildWhen: (_, s) => s is SelectItemChangeState, 349 buildWhen: (_, s) => s is SelectItemChangeState,
350 builder: (context, state) { 350 builder: (context, state) {
351 final bloc = BlocProvider.of<TopicPictureBloc>(context); 351 final bloc = BlocProvider.of<TopicPictureBloc>(context);
352 - final isAnswerOption = bloc.selectItem == index; 352 + final isAnswerOption = bloc.optionSelectItem == index;
353 final answerCorrect = 353 final answerCorrect =
354 isAnswerOption && bloc.checkAnswerRight(index) == true; 354 isAnswerOption && bloc.checkAnswerRight(index) == true;
355 return OptionWidget( 355 return OptionWidget(
@@ -423,7 +423,7 @@ class _TopicPicturePage extends StatelessWidget { @@ -423,7 +423,7 @@ class _TopicPicturePage extends StatelessWidget {
423 buildWhen: (_, s) => s is SelectItemChangeState, 423 buildWhen: (_, s) => s is SelectItemChangeState,
424 builder: (context, state) { 424 builder: (context, state) {
425 final bloc = BlocProvider.of<TopicPictureBloc>(context); 425 final bloc = BlocProvider.of<TopicPictureBloc>(context);
426 - final isAnswerOption = bloc.selectItem == index; 426 + final isAnswerOption = bloc.optionSelectItem == index;
427 final answerCorrect = 427 final answerCorrect =
428 isAnswerOption && bloc.checkAnswerRight(index) == true; 428 isAnswerOption && bloc.checkAnswerRight(index) == true;
429 return OptionWidget( 429 return OptionWidget(
@@ -522,8 +522,6 @@ class _TopicPicturePage extends StatelessWidget { @@ -522,8 +522,6 @@ class _TopicPicturePage extends StatelessWidget {
522 70.verticalSpace, 522 70.verticalSpace,
523 RecorderWidget( 523 RecorderWidget(
524 isPlaying: bloc.isRecording, 524 isPlaying: bloc.isRecording,
525 - isClickable:  
526 - bloc.voicePlayState != VoicePlayState.playing,  
527 width: 72.w, 525 width: 72.w,
528 height: 72.w, 526 height: 72.w,
529 onTap: () { 527 onTap: () {
lib/utils/audio_player_util.dart
@@ -18,7 +18,9 @@ enum AudioPlayerUtilType { @@ -18,7 +18,9 @@ enum AudioPlayerUtilType {
18 excellent('excellent'), 18 excellent('excellent'),
19 great('great'), 19 great('great'),
20 good('good'), 20 good('good'),
21 - tryAgain('try_again'); 21 + tryAgain('try_again'),
  22 + right('right'),
  23 + wrong('wrong');
22 24
23 const AudioPlayerUtilType(this.path); 25 const AudioPlayerUtilType(this.path);
24 26