Commit 819ae43b6372c182f6ab91a6313e226000746817
1 parent
081fbff7
feat:体验优化-练习题目取消阻塞,支持任何条件下的点击,增加体验流畅感
Showing
8 changed files
with
125 additions
and
148 deletions
lib/common/utils/click_with_music_controller.dart
| ... | ... | @@ -11,6 +11,8 @@ class ClickWithMusicController { |
| 11 | 11 | |
| 12 | 12 | static ClickWithMusicController? _instance; |
| 13 | 13 | |
| 14 | + static String TAG = 'ClickWithMusicController'; | |
| 15 | + | |
| 14 | 16 | ClickWithMusicController._privateConstructor(); |
| 15 | 17 | |
| 16 | 18 | static ClickWithMusicController get instance => _instance ??= ClickWithMusicController._privateConstructor(); |
| ... | ... | @@ -21,11 +23,14 @@ class ClickWithMusicController { |
| 21 | 23 | ///@param action 可以是同步函数也可以是异步函数 |
| 22 | 24 | Future<void> playMusicAndPerformAction(BuildContext? context, |
| 23 | 25 | AudioPlayerUtilType audioType, FutureOr<void> Function() action) async { |
| 26 | + Log.d("$TAG playMusicAndPerformAction _isPlaying=$_isPlaying"); | |
| 27 | + ///todo 是否需要考虑打断覆盖能力 | |
| 24 | 28 | if (_isPlaying) return; |
| 25 | 29 | |
| 26 | 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 | 34 | // Play the music |
| 30 | 35 | await AudioPlayerUtil.getInstance() |
| 31 | 36 | .playAudio(audioType); |
| ... | ... | @@ -33,15 +38,16 @@ class ClickWithMusicController { |
| 33 | 38 | try { |
| 34 | 39 | await Future.sync(action); |
| 35 | 40 | } catch (e) { |
| 36 | - Log.d('WQF playMusicAndPerformAction exception $e'); | |
| 41 | + Log.d('$TAG playMusicAndPerformAction exception $e'); | |
| 37 | 42 | } finally { |
| 38 | - Log.d("WQF playMusicAndPerformAction playAudio end"); | |
| 43 | + Log.d("$TAG playMusicAndPerformAction playAudio end"); | |
| 39 | 44 | _isPlaying = false; |
| 40 | 45 | } |
| 41 | 46 | } |
| 42 | 47 | |
| 43 | - void reset() { | |
| 48 | + Future<void> reset() async { | |
| 44 | 49 | _isPlaying = false; |
| 50 | + await AudioPlayerUtil.getInstance().stop(); | |
| 45 | 51 | } |
| 46 | 52 | |
| 47 | 53 | // void dispose() { | ... | ... |
lib/common/widgets/cheer_reward_widget.dart
| ... | ... | @@ -54,13 +54,13 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget> |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | 56 | void _startAnimation() { |
| 57 | - Log.d("$TAG _startAnimation"); | |
| 57 | + Log.d("$TAG ${identityHashCode(this)} _startAnimation"); | |
| 58 | 58 | setState(() { |
| 59 | 59 | _isVisible = true; |
| 60 | 60 | }); |
| 61 | 61 | |
| 62 | 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 | 64 | _controller.duration = composition.duration; |
| 65 | 65 | _controller.forward().whenCompleteOrCancel(() { |
| 66 | 66 | if (mounted) { | ... | ... |
lib/common/widgets/recorder_widget.dart
lib/common/widgets/speaker_widget.dart
| ... | ... | @@ -52,7 +52,10 @@ class _SpeakerWidgetState extends State<SpeakerWidget> |
| 52 | 52 | Log.d( |
| 53 | 53 | "$TAG ${identityHashCode(this)} initState widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller=${identityHashCode(_controller)}"); |
| 54 | 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 | 30 | \ No newline at end of file | ... | ... |
lib/pages/practice/bloc/topic_picture_bloc.dart
| ... | ... | @@ -20,8 +20,8 @@ import 'package:wow_english/utils/toast_util.dart'; |
| 20 | 20 | import '../../../common/permission/permissionRequester.dart'; |
| 21 | 21 | import '../../../common/utils/click_with_music_controller.dart'; |
| 22 | 22 | import '../../../common/utils/show_star_reward_dialog.dart'; |
| 23 | +import '../../../models/voice_result_type.dart'; | |
| 23 | 24 | import '../../../route/route.dart'; |
| 24 | -import '../../../utils/log_util.dart'; | |
| 25 | 25 | |
| 26 | 26 | part 'topic_picture_event.dart'; |
| 27 | 27 | |
| ... | ... | @@ -49,7 +49,7 @@ class TopicPictureBloc |
| 49 | 49 | |
| 50 | 50 | int _currentPage = 0; |
| 51 | 51 | |
| 52 | - int _selectItem = -1; | |
| 52 | + int _optionSelectItem = -1; | |
| 53 | 53 | |
| 54 | 54 | CourseProcessEntity? _entity; |
| 55 | 55 | |
| ... | ... | @@ -61,19 +61,10 @@ class TopicPictureBloc |
| 61 | 61 | ///正在播放音频 |
| 62 | 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 | 64 | int get currentPage => _currentPage + 1; |
| 75 | 65 | |
| 76 | - int get selectItem => _selectItem; | |
| 66 | + /// 选择题选中项 | |
| 67 | + int get optionSelectItem => _optionSelectItem; | |
| 77 | 68 | |
| 78 | 69 | bool get isRecording => _isRecording; |
| 79 | 70 | |
| ... | ... | @@ -103,46 +94,24 @@ class TopicPictureBloc |
| 103 | 94 | //音频播放器 |
| 104 | 95 | audioPlayer = AudioPlayer(); |
| 105 | 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 | 117 | methodChannel = |
| ... | ... | @@ -191,8 +160,6 @@ class TopicPictureBloc |
| 191 | 160 | pageController.dispose(); |
| 192 | 161 | audioPlayer.release(); |
| 193 | 162 | audioPlayer.dispose(); |
| 194 | - _isResultSoundPlaying = false; | |
| 195 | - _forbiddenWhenCorrect = false; | |
| 196 | 163 | _voiceXsCancel(); |
| 197 | 164 | return super.close(); |
| 198 | 165 | } |
| ... | ... | @@ -213,7 +180,7 @@ class TopicPictureBloc |
| 213 | 180 | ///页面切换 |
| 214 | 181 | void _pageControllerChange(CurrentPageIndexChangeEvent event, |
| 215 | 182 | Emitter<TopicPictureState> emitter) async { |
| 216 | - await closePlayerResource(); | |
| 183 | + await pageResetIfNeed(); | |
| 217 | 184 | debugPrint('翻页 $_currentPage->${event.pageIndex}'); |
| 218 | 185 | if (_currentPage == _entity?.topics?.length) { |
| 219 | 186 | return; |
| ... | ... | @@ -229,26 +196,22 @@ class TopicPictureBloc |
| 229 | 196 | } |
| 230 | 197 | } |
| 231 | 198 | } |
| 232 | - _selectItem = -1; | |
| 233 | 199 | emitter(CurrentPageIndexState()); |
| 234 | 200 | } |
| 235 | 201 | |
| 236 | 202 | ///选择 |
| 237 | 203 | void _selectItemLoad( |
| 238 | 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 | 210 | showStarRewardDialog(context); |
| 246 | - // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); | |
| 211 | + await _playResultSound(true); | |
| 247 | 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 | 253 | } |
| 291 | 254 | |
| 292 | 255 | ///终止评测 |
| 293 | - void _voiceXsStop( | |
| 256 | + Future<void> _voiceXsStop( | |
| 294 | 257 | XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async { |
| 295 | 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 | 269 | void _voiceXsResult( |
| 305 | 270 | XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async { |
| 271 | + _isRecording = false; | |
| 272 | + emitter(XSVoiceTestState()); | |
| 306 | 273 | final Map args = event.message as Map; |
| 307 | 274 | final result = args['result'] as Map; |
| 308 | 275 | final overall = result['overall'].toString(); |
| 309 | 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 | 295 | // 题目音频播放 |
| 365 | 296 | void _questionVoicePlay( |
| 366 | 297 | VoicePlayEvent event, Emitter<TopicPictureState> emitter) async { |
| 367 | - if (_forbiddenWhenCorrect) { | |
| 368 | - return; | |
| 369 | - } | |
| 370 | - _forbiddenWhenCorrect = false; | |
| 371 | - await closePlayerResource(); | |
| 298 | + await pageResetIfNeed(); | |
| 372 | 299 | final topics = _entity?.topics?[_currentPage]; |
| 373 | 300 | final urlStr = topics?.audioUrl ?? ''; |
| 374 | 301 | await audioPlayer.play(UrlSource(urlStr), |
| 375 | 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 | 315 | Future<void> closePlayerResource() async { |
| 379 | - if (voicePlayState == VoicePlayState.playing || _isResultSoundPlaying) { | |
| 316 | + if (voicePlayState == VoicePlayState.playing) { | |
| 380 | 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 | 173 | buildWhen: (_, s) => s is SelectItemChangeState, |
| 174 | 174 | builder: (context, state) { |
| 175 | 175 | final bloc = BlocProvider.of<TopicPictureBloc>(context); |
| 176 | - final isAnswerOption = bloc.selectItem == index; | |
| 176 | + final isAnswerOption = bloc.optionSelectItem == index; | |
| 177 | 177 | final answerCorrect = |
| 178 | 178 | isAnswerOption && bloc.checkAnswerRight(index) == true; |
| 179 | 179 | return Container( |
| ... | ... | @@ -243,7 +243,7 @@ class _TopicPicturePage extends StatelessWidget { |
| 243 | 243 | buildWhen: (_, s) => s is SelectItemChangeState, |
| 244 | 244 | builder: (context, state) { |
| 245 | 245 | final bloc = BlocProvider.of<TopicPictureBloc>(context); |
| 246 | - final isAnswerOption = bloc.selectItem == index; | |
| 246 | + final isAnswerOption = bloc.optionSelectItem == index; | |
| 247 | 247 | final answerCorrect = |
| 248 | 248 | isAnswerOption && bloc.checkAnswerRight(index) == true; |
| 249 | 249 | return Container( |
| ... | ... | @@ -349,7 +349,7 @@ class _TopicPicturePage extends StatelessWidget { |
| 349 | 349 | buildWhen: (_, s) => s is SelectItemChangeState, |
| 350 | 350 | builder: (context, state) { |
| 351 | 351 | final bloc = BlocProvider.of<TopicPictureBloc>(context); |
| 352 | - final isAnswerOption = bloc.selectItem == index; | |
| 352 | + final isAnswerOption = bloc.optionSelectItem == index; | |
| 353 | 353 | final answerCorrect = |
| 354 | 354 | isAnswerOption && bloc.checkAnswerRight(index) == true; |
| 355 | 355 | return OptionWidget( |
| ... | ... | @@ -423,7 +423,7 @@ class _TopicPicturePage extends StatelessWidget { |
| 423 | 423 | buildWhen: (_, s) => s is SelectItemChangeState, |
| 424 | 424 | builder: (context, state) { |
| 425 | 425 | final bloc = BlocProvider.of<TopicPictureBloc>(context); |
| 426 | - final isAnswerOption = bloc.selectItem == index; | |
| 426 | + final isAnswerOption = bloc.optionSelectItem == index; | |
| 427 | 427 | final answerCorrect = |
| 428 | 428 | isAnswerOption && bloc.checkAnswerRight(index) == true; |
| 429 | 429 | return OptionWidget( |
| ... | ... | @@ -522,8 +522,6 @@ class _TopicPicturePage extends StatelessWidget { |
| 522 | 522 | 70.verticalSpace, |
| 523 | 523 | RecorderWidget( |
| 524 | 524 | isPlaying: bloc.isRecording, |
| 525 | - isClickable: | |
| 526 | - bloc.voicePlayState != VoicePlayState.playing, | |
| 527 | 525 | width: 72.w, |
| 528 | 526 | height: 72.w, |
| 529 | 527 | onTap: () { | ... | ... |
lib/utils/audio_player_util.dart