Commit 065022b77e5d916f521551d108d7eadd9a839516
1 parent
cc3bbe3d
feat:绘本评测反馈弹窗+原音播放&录音播放&开始录音互斥逻辑
Showing
13 changed files
with
242 additions
and
46 deletions
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
| @@ -61,10 +61,16 @@ class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEn | @@ -61,10 +61,16 @@ class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEn | ||
| 61 | SingEngineHelper.addOnResultListener(this) | 61 | SingEngineHelper.addOnResultListener(this) |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | - override fun onResult(jsonObject: JSONObject, evalType: Int?) { | 64 | + override fun onResult(map: Map<String, Any>, evalType: Int?) { |
| 65 | //先声回调在子线程,需要切换到主线程 | 65 | //先声回调在子线程,需要切换到主线程 |
| 66 | GlobalHandler.runOnMainThread { | 66 | GlobalHandler.runOnMainThread { |
| 67 | - invokeMethod("voiceResult", jsonObject.toString()) | 67 | + invokeMethod("voiceResult", map) |
| 68 | + } | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + override fun onRecordFail(code: Int, message: String) { | ||
| 72 | + GlobalHandler.runOnMainThread { | ||
| 73 | + invokeMethod("voiceFail", mapOf("code" to code, "message" to message)) | ||
| 68 | } | 74 | } |
| 69 | } | 75 | } |
| 70 | 76 |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/OnSingEngineLifecycles.kt
| @@ -14,11 +14,18 @@ interface SingEngineLifecycles { | @@ -14,11 +14,18 @@ interface SingEngineLifecycles { | ||
| 14 | fun onRecordPlayOver() | 14 | fun onRecordPlayOver() |
| 15 | 15 | ||
| 16 | // 评测完成 | 16 | // 评测完成 |
| 17 | - fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE) | 17 | + fun onResult(map: Map<String, Any>, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE) |
| 18 | 18 | ||
| 19 | // 取消评测 | 19 | // 取消评测 |
| 20 | fun onCancel() | 20 | fun onCancel() |
| 21 | 21 | ||
| 22 | + /** | ||
| 23 | + * 评测失败 | ||
| 24 | + * @param code 失败错误码 | ||
| 25 | + * @param message 失败错误信息 | ||
| 26 | + */ | ||
| 27 | + fun onRecordFail(code: Int, message: String) | ||
| 28 | + | ||
| 22 | 29 | ||
| 23 | abstract class OnSingEngineAdapter : SingEngineLifecycles { | 30 | abstract class OnSingEngineAdapter : SingEngineLifecycles { |
| 24 | override fun onRecordBegin() { | 31 | override fun onRecordBegin() { |
| @@ -33,12 +40,16 @@ interface SingEngineLifecycles { | @@ -33,12 +40,16 @@ interface SingEngineLifecycles { | ||
| 33 | 40 | ||
| 34 | } | 41 | } |
| 35 | 42 | ||
| 36 | - override fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int?) { | 43 | + override fun onResult(map: Map<String, Any>, @EvalTargetType evalType: Int?) { |
| 37 | 44 | ||
| 38 | } | 45 | } |
| 39 | 46 | ||
| 40 | override fun onCancel() { | 47 | override fun onCancel() { |
| 41 | 48 | ||
| 42 | } | 49 | } |
| 50 | + | ||
| 51 | + override fun onRecordFail(code: Int, message: String) { | ||
| 52 | + | ||
| 53 | + } | ||
| 43 | } | 54 | } |
| 44 | } | 55 | } |
| 45 | \ No newline at end of file | 56 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt
| @@ -9,6 +9,7 @@ import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType | @@ -9,6 +9,7 @@ import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType | ||
| 9 | import com.kouyuxingqiu.wow_english.singsound.config.SingSoundConfig | 9 | import com.kouyuxingqiu.wow_english.singsound.config.SingSoundConfig |
| 10 | import com.kouyuxingqiu.wow_english.singsound.config.VoiceConfig | 10 | import com.kouyuxingqiu.wow_english.singsound.config.VoiceConfig |
| 11 | import com.kouyuxingqiu.wow_english.singsound.config.WordConfig | 11 | import com.kouyuxingqiu.wow_english.singsound.config.WordConfig |
| 12 | +import com.kouyuxingqiu.wow_english.singsound.util.JsonUtils.toMap | ||
| 12 | import com.xs.SingEngine | 13 | import com.xs.SingEngine |
| 13 | import com.xs.impl.AudioErrorCallback | 14 | import com.xs.impl.AudioErrorCallback |
| 14 | import com.xs.impl.EvalReturnRequestIdCallback | 15 | import com.xs.impl.EvalReturnRequestIdCallback |
| @@ -279,11 +280,10 @@ object SingEngineHelper : | @@ -279,11 +280,10 @@ object SingEngineHelper : | ||
| 279 | */ | 280 | */ |
| 280 | override fun onResult(jsonObject: JSONObject) { | 281 | override fun onResult(jsonObject: JSONObject) { |
| 281 | Log.i(TAG, "onResult = $jsonObject") | 282 | Log.i(TAG, "onResult = $jsonObject") |
| 282 | - parseResult(jsonObject) | ||
| 283 | setTokenToCache(jsonObject) | 283 | setTokenToCache(jsonObject) |
| 284 | mListeners?.let { | 284 | mListeners?.let { |
| 285 | for (callback in it) { | 285 | for (callback in it) { |
| 286 | - callback.onResult(jsonObject, mCurEvalType) | 286 | + callback.onResult(toMap(jsonObject), mCurEvalType) |
| 287 | } | 287 | } |
| 288 | } | 288 | } |
| 289 | } | 289 | } |
| @@ -376,6 +376,13 @@ object SingEngineHelper : | @@ -376,6 +376,13 @@ object SingEngineHelper : | ||
| 376 | */ | 376 | */ |
| 377 | override fun onEnd(resultBody: ResultBody) { | 377 | override fun onEnd(resultBody: ResultBody) { |
| 378 | Log.i(TAG, "onEnd resultBody=$resultBody") | 378 | Log.i(TAG, "onEnd resultBody=$resultBody") |
| 379 | + if (resultBody.code != 0) { | ||
| 380 | + mListeners?.let { | ||
| 381 | + for (callback in it) { | ||
| 382 | + callback.onRecordFail(resultBody.code, resultBody.message) | ||
| 383 | + } | ||
| 384 | + } | ||
| 385 | + } | ||
| 379 | } | 386 | } |
| 380 | 387 | ||
| 381 | override fun onGetEvalRequestId(p0: String?) { | 388 | override fun onGetEvalRequestId(p0: String?) { |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/util/JsonUtils.java
| @@ -2,10 +2,21 @@ package com.kouyuxingqiu.wow_english.singsound.util; | @@ -2,10 +2,21 @@ package com.kouyuxingqiu.wow_english.singsound.util; | ||
| 2 | 2 | ||
| 3 | import android.util.Log; | 3 | import android.util.Log; |
| 4 | 4 | ||
| 5 | +import com.google.gson.Gson; | ||
| 6 | +import com.google.gson.JsonElement; | ||
| 7 | +import com.google.gson.JsonObject; | ||
| 8 | + | ||
| 5 | import org.json.JSONArray; | 9 | import org.json.JSONArray; |
| 6 | import org.json.JSONException; | 10 | import org.json.JSONException; |
| 7 | import org.json.JSONObject; | 11 | import org.json.JSONObject; |
| 8 | 12 | ||
| 13 | +import java.util.ArrayList; | ||
| 14 | +import java.util.HashMap; | ||
| 15 | +import java.util.Iterator; | ||
| 16 | +import java.util.List; | ||
| 17 | +import java.util.Map; | ||
| 18 | +import java.util.Set; | ||
| 19 | + | ||
| 9 | /** | 20 | /** |
| 10 | * Created by wangz on 2017/8/29. | 21 | * Created by wangz on 2017/8/29. |
| 11 | */ | 22 | */ |
| @@ -84,4 +95,42 @@ public class JsonUtils { | @@ -84,4 +95,42 @@ public class JsonUtils { | ||
| 84 | return false; | 95 | return false; |
| 85 | } | 96 | } |
| 86 | } | 97 | } |
| 98 | + | ||
| 99 | + public static Map<String, Object> toMap(JSONObject jsonObject) throws JSONException { | ||
| 100 | + Map<String, Object> map = new HashMap<>(); | ||
| 101 | + Iterator<String> keysIterator = jsonObject.keys(); | ||
| 102 | + while (keysIterator.hasNext()) { | ||
| 103 | + String key = keysIterator.next(); | ||
| 104 | + Object value = jsonObject.get(key); | ||
| 105 | + if (value instanceof JSONObject) { | ||
| 106 | + value = toMap((JSONObject) value); | ||
| 107 | + } | ||
| 108 | + if (value instanceof JSONArray) { | ||
| 109 | + value = toList((JSONArray) value); | ||
| 110 | + } | ||
| 111 | + map.put(key, value); | ||
| 112 | + } | ||
| 113 | + return map; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + public static List<Object> toList(JSONArray jsonArray) throws JSONException { | ||
| 117 | + List<Object> list = new ArrayList<>(); | ||
| 118 | + for (int i = 0; i < jsonArray.length(); i++) { | ||
| 119 | + Object value = jsonArray.get(i); | ||
| 120 | + if (value instanceof JSONObject || value instanceof JSONArray) { | ||
| 121 | + value = toObject(value); | ||
| 122 | + } | ||
| 123 | + list.add(value); | ||
| 124 | + } | ||
| 125 | + return list; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + public static Object toObject(Object json) throws JSONException { | ||
| 129 | + if (json instanceof JSONObject) { | ||
| 130 | + return toMap((JSONObject) json); | ||
| 131 | + } else if (json instanceof JSONArray) { | ||
| 132 | + return toList((JSONArray) json); | ||
| 133 | + } | ||
| 134 | + return json; | ||
| 135 | + } | ||
| 87 | } | 136 | } |
assets/images/pic_very_good.webp
0 → 100644
No preview for this file type
assets/images/record_pause.webp
No preview for this file type
assets/images/record_play.webp
No preview for this file type
assets/images/text_very_good.webp
0 → 100644
No preview for this file type
lib/pages/reading/bloc/reading_bloc.dart
| @@ -12,6 +12,8 @@ import '../../../models/course_process_entity.dart'; | @@ -12,6 +12,8 @@ import '../../../models/course_process_entity.dart'; | ||
| 12 | import '../../../utils/loading.dart'; | 12 | import '../../../utils/loading.dart'; |
| 13 | import 'dart:convert'; | 13 | import 'dart:convert'; |
| 14 | 14 | ||
| 15 | +import '../../../utils/log_util.dart'; | ||
| 16 | + | ||
| 15 | part 'reading_event.dart'; | 17 | part 'reading_event.dart'; |
| 16 | part 'reading_state.dart'; | 18 | part 'reading_state.dart'; |
| 17 | 19 | ||
| @@ -38,7 +40,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -38,7 +40,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 38 | int _currentPage = 0; | 40 | int _currentPage = 0; |
| 39 | 41 | ||
| 40 | ///当前播放模式 | 42 | ///当前播放模式 |
| 41 | - ReadingModeType _currentMode = ReadingModeType.auto; | 43 | + ReadingModeType _currentMode = ReadingModeType.manual; |
| 42 | 44 | ||
| 43 | int get currentPage => _currentPage + 1; | 45 | int get currentPage => _currentPage + 1; |
| 44 | 46 | ||
| @@ -88,10 +90,12 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -88,10 +90,12 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 88 | if (event == PlayerState.completed) { | 90 | if (event == PlayerState.completed) { |
| 89 | debugPrint('播放完成'); | 91 | debugPrint('播放完成'); |
| 90 | _voicePlayState = VoicePlayState.completed; | 92 | _voicePlayState = VoicePlayState.completed; |
| 93 | + _onAudioPlayComplete(); | ||
| 91 | } | 94 | } |
| 92 | if (event == PlayerState.stopped) { | 95 | if (event == PlayerState.stopped) { |
| 93 | debugPrint('播放结束'); | 96 | debugPrint('播放结束'); |
| 94 | _voicePlayState = VoicePlayState.stop; | 97 | _voicePlayState = VoicePlayState.stop; |
| 98 | + _onAudioPlayComplete(); | ||
| 95 | } | 99 | } |
| 96 | 100 | ||
| 97 | if (event == PlayerState.playing) { | 101 | if (event == PlayerState.playing) { |
| @@ -108,6 +112,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -108,6 +112,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 108 | const MethodChannel('wow_english/sing_sound_method_channel'); | 112 | const MethodChannel('wow_english/sing_sound_method_channel'); |
| 109 | methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 | 113 | methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 |
| 110 | methodChannel.setMethodCallHandler((call) async { | 114 | methodChannel.setMethodCallHandler((call) async { |
| 115 | + Log.d("setMethodCallHandler method=${call.method} arguments=${call.arguments}"); | ||
| 111 | if (call.method == 'voiceResult') { | 116 | if (call.method == 'voiceResult') { |
| 112 | //评测结果 | 117 | //评测结果 |
| 113 | add(XSVoiceResultEvent(call.arguments)); | 118 | add(XSVoiceResultEvent(call.arguments)); |
| @@ -119,6 +124,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -119,6 +124,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 119 | if (kDebugMode) { | 124 | if (kDebugMode) { |
| 120 | print('评测开始'); | 125 | print('评测开始'); |
| 121 | } | 126 | } |
| 127 | + _isRecording = true; | ||
| 128 | + add(OnXSVoiceStateChangeEvent()); | ||
| 122 | return; | 129 | return; |
| 123 | } | 130 | } |
| 124 | 131 | ||
| @@ -127,6 +134,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -127,6 +134,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 127 | if (kDebugMode) { | 134 | if (kDebugMode) { |
| 128 | print('评测结束'); | 135 | print('评测结束'); |
| 129 | } | 136 | } |
| 137 | + _isRecording = false; | ||
| 138 | + add(OnXSVoiceStateChangeEvent()); | ||
| 130 | return; | 139 | return; |
| 131 | } | 140 | } |
| 132 | 141 | ||
| @@ -143,6 +152,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -143,6 +152,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 143 | on<XSVoiceStartEvent>(_voiceXsStart); | 152 | on<XSVoiceStartEvent>(_voiceXsStart); |
| 144 | on<XSVoiceStopEvent>(_voiceXsStop); | 153 | on<XSVoiceStopEvent>(_voiceXsStop); |
| 145 | on<XSVoiceResultEvent>(_voiceXsResult); | 154 | on<XSVoiceResultEvent>(_voiceXsResult); |
| 155 | + on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange); | ||
| 146 | 156 | ||
| 147 | on<PlayRecordAudioEvent>(_playRecordAudio); | 157 | on<PlayRecordAudioEvent>(_playRecordAudio); |
| 148 | } | 158 | } |
| @@ -178,7 +188,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -178,7 +188,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 178 | try { | 188 | try { |
| 179 | await loading(() async { | 189 | await loading(() async { |
| 180 | _entity = await ListenDao.process(courseLessonId); | 190 | _entity = await ListenDao.process(courseLessonId); |
| 181 | - print("reading page entity: ${_entity!.toJson()}"); | 191 | + Log.d("reading page entity: ${_entity!.toJson()}"); |
| 182 | emitter(RequestDataState()); | 192 | emitter(RequestDataState()); |
| 183 | }); | 193 | }); |
| 184 | } catch (e) { | 194 | } catch (e) { |
| @@ -194,11 +204,20 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -194,11 +204,20 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 194 | _playOriginalAudioInner(event.url); | 204 | _playOriginalAudioInner(event.url); |
| 195 | } | 205 | } |
| 196 | 206 | ||
| 197 | - void _playOriginalAudioInner(String? audioUrl) { | ||
| 198 | - print("_playOriginalAudio url=$audioUrl"); | ||
| 199 | - audioUrl ??= currentPageData()?.audioUrl ?? ''; | ||
| 200 | - _playAudio(audioUrl); | ||
| 201 | - _isOriginAudioPlaying = true; | 207 | + ///播放原音音频 |
| 208 | + Future<void> _playOriginalAudioInner(String? audioUrl) async { | ||
| 209 | + if (_isRecordAudioPlaying) { | ||
| 210 | + _isRecordAudioPlaying = false; | ||
| 211 | + } | ||
| 212 | + Log.d("_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl"); | ||
| 213 | + if (_isOriginAudioPlaying) { | ||
| 214 | + _isOriginAudioPlaying = false; | ||
| 215 | + await audioPlayer.stop(); | ||
| 216 | + } else { | ||
| 217 | + _isOriginAudioPlaying = true; | ||
| 218 | + audioUrl ??= currentPageData()?.audioUrl ?? ''; | ||
| 219 | + _playAudio(audioUrl); | ||
| 220 | + } | ||
| 202 | } | 221 | } |
| 203 | 222 | ||
| 204 | /// 播放录音 | 223 | /// 播放录音 |
| @@ -207,22 +226,29 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -207,22 +226,29 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 207 | _playRecordAudioInner(); | 226 | _playRecordAudioInner(); |
| 208 | } | 227 | } |
| 209 | 228 | ||
| 210 | - void _playRecordAudioInner() { | ||
| 211 | - final recordAudioUrl = currentPageData()?.recordUrl; | ||
| 212 | - print("_playRecordAudioInner url=${currentPageData()?.recordUrl}"); | ||
| 213 | - _playAudio(recordAudioUrl); | ||
| 214 | - _isRecordAudioPlaying = true; | 229 | + Future<void> _playRecordAudioInner() async { |
| 230 | + if (_isOriginAudioPlaying) { | ||
| 231 | + _isOriginAudioPlaying = false; | ||
| 232 | + } | ||
| 233 | + Log.d("_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}"); | ||
| 234 | + if (_isRecordAudioPlaying) { | ||
| 235 | + _isRecordAudioPlaying = false; | ||
| 236 | + await audioPlayer.stop(); | ||
| 237 | + } else { | ||
| 238 | + _isRecordAudioPlaying = true; | ||
| 239 | + final recordAudioUrl = currentPageData()?.recordUrl; | ||
| 240 | + _playAudio(recordAudioUrl); | ||
| 241 | + } | ||
| 242 | + // emit(VoicePlayStateChange()); | ||
| 215 | } | 243 | } |
| 216 | 244 | ||
| 217 | void _playAudio(String? audioUrl) async { | 245 | void _playAudio(String? audioUrl) async { |
| 218 | - await audioPlayer.stop(); | ||
| 219 | if (audioUrl!.isNotEmpty) { | 246 | if (audioUrl!.isNotEmpty) { |
| 220 | await audioPlayer.play(UrlSource(audioUrl)); | 247 | await audioPlayer.play(UrlSource(audioUrl)); |
| 221 | } | 248 | } |
| 222 | } | 249 | } |
| 223 | 250 | ||
| 224 | int dataCount() { | 251 | int dataCount() { |
| 225 | - // print("dataCount=${_entity?.readings?.length ?? 0}"); | ||
| 226 | return _entity?.readings?.length ?? 0; | 252 | return _entity?.readings?.length ?? 0; |
| 227 | } | 253 | } |
| 228 | 254 | ||
| @@ -230,6 +256,18 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -230,6 +256,18 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 230 | return _entity?.readings?[_currentPage]; | 256 | return _entity?.readings?[_currentPage]; |
| 231 | } | 257 | } |
| 232 | 258 | ||
| 259 | + void nextPage() { | ||
| 260 | + if (_currentPage >= dataCount() - 1) { | ||
| 261 | + ///todo 最后一页了 | ||
| 262 | + } else { | ||
| 263 | + _currentPage += 1; | ||
| 264 | + pageController.nextPage( | ||
| 265 | + duration: const Duration(milliseconds: 500), | ||
| 266 | + curve: Curves.ease, | ||
| 267 | + ); | ||
| 268 | + } | ||
| 269 | + } | ||
| 270 | + | ||
| 233 | ///初始化SDK | 271 | ///初始化SDK |
| 234 | _initVoiceSdk( | 272 | _initVoiceSdk( |
| 235 | XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { | 273 | XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { |
| @@ -241,7 +279,6 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -241,7 +279,6 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 241 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { | 279 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { |
| 242 | _stopAudio(); | 280 | _stopAudio(); |
| 243 | startRecord(event.content); | 281 | startRecord(event.content); |
| 244 | - emitter(XSVoiceTestState()); | ||
| 245 | } | 282 | } |
| 246 | 283 | ||
| 247 | void startRecord(String content) async { | 284 | void startRecord(String content) async { |
| @@ -249,27 +286,22 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -249,27 +286,22 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 249 | return; | 286 | return; |
| 250 | } | 287 | } |
| 251 | methodChannel.invokeMethod( | 288 | methodChannel.invokeMethod( |
| 252 | - 'startVoice', {'word': 'how old are you', 'type': '0', 'userId': '1'}); | ||
| 253 | - _isRecording = true; | 289 | + 'startVoice', {'word': content, 'type': '0', 'userId': '1'}); |
| 254 | } | 290 | } |
| 255 | 291 | ||
| 256 | void _voiceXsResult( | 292 | void _voiceXsResult( |
| 257 | XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async { | 293 | XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async { |
| 258 | - final Map args = json.decode(event.message); | ||
| 259 | - final result = args['result']; | ||
| 260 | - print("_voiceXsResult result=${result}"); | ||
| 261 | - if (result != null) { | ||
| 262 | - final overall = result['overall'].toString(); | ||
| 263 | - EasyLoading.showToast('测评成功,分数是$overall', | ||
| 264 | - duration: const Duration(seconds: 10)); | ||
| 265 | - currentPageData()?.recordScore = overall; | ||
| 266 | - currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | ||
| 267 | - _playRecordAudioInner(); | ||
| 268 | - } else { | ||
| 269 | - EasyLoading.showToast('测评失败', duration: const Duration(seconds: 10)); | ||
| 270 | - } | ||
| 271 | - _isRecording = false; | ||
| 272 | - emitter(XSVoiceTestState()); | 294 | + final Map args = event.message as Map; |
| 295 | + final result = args['result'] as Map; | ||
| 296 | + Log.d("_voiceXsResult result=$result"); | ||
| 297 | + final overall = result['overall'].toString(); | ||
| 298 | + EasyLoading.showToast('测评成功,分数是$overall', | ||
| 299 | + duration: const Duration(seconds: 10)); | ||
| 300 | + currentPageData()?.recordScore = overall; | ||
| 301 | + currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | ||
| 302 | + ///完成录音后紧接着播放录音 | ||
| 303 | + _playRecordAudioInner(); | ||
| 304 | + emitter(FeedbackState()); | ||
| 273 | } | 305 | } |
| 274 | 306 | ||
| 275 | ///终止评测 | 307 | ///终止评测 |
| @@ -283,9 +315,32 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -283,9 +315,32 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 283 | emitter(VoicePlayStateChange()); | 315 | emitter(VoicePlayStateChange()); |
| 284 | } | 316 | } |
| 285 | 317 | ||
| 318 | + void _onAudioPlayComplete() { | ||
| 319 | + if (_isRecordAudioPlaying && _currentMode == ReadingModeType.auto) { | ||
| 320 | + nextPage(); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + Log.d("_onAudioPlayComplete _isOriginAudioPlaying=${_isOriginAudioPlaying} _voicePlayState=$_voicePlayState recordUrl=${currentPageData()?.recordUrl?.isNotEmpty}"); | ||
| 324 | + if (_isOriginAudioPlaying && _voicePlayState == VoicePlayState.completed && currentPageData()?.recordUrl?.isNotEmpty != true) { | ||
| 325 | + ///如果刚刚完成原音播放&&录音为空,则开始录音 | ||
| 326 | + startRecord(currentPageData()?.word ?? ''); | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + _isOriginAudioPlaying = false; | ||
| 330 | + _isRecordAudioPlaying = false; | ||
| 331 | + } | ||
| 332 | + | ||
| 286 | void _stopAudio() async { | 333 | void _stopAudio() async { |
| 287 | await audioPlayer.stop(); | 334 | await audioPlayer.stop(); |
| 288 | _isOriginAudioPlaying = false; | 335 | _isOriginAudioPlaying = false; |
| 289 | _isRecordAudioPlaying = false; | 336 | _isRecordAudioPlaying = false; |
| 290 | } | 337 | } |
| 338 | + | ||
| 339 | + void _onVoiceXsStateChange( | ||
| 340 | + OnXSVoiceStateChangeEvent event, | ||
| 341 | + Emitter<ReadingPageState> emitter | ||
| 342 | + ) async { | ||
| 343 | + emit(XSVoiceTestState()); | ||
| 344 | + } | ||
| 291 | } | 345 | } |
| 346 | + |
lib/pages/reading/bloc/reading_event.dart
| @@ -45,6 +45,9 @@ class XSVoiceStartEvent extends ReadingPageEvent { | @@ -45,6 +45,9 @@ class XSVoiceStartEvent extends ReadingPageEvent { | ||
| 45 | ///先声评测停止 | 45 | ///先声评测停止 |
| 46 | class XSVoiceStopEvent extends ReadingPageEvent {} | 46 | class XSVoiceStopEvent extends ReadingPageEvent {} |
| 47 | 47 | ||
| 48 | +///先声评测状态 | ||
| 49 | +class OnXSVoiceStateChangeEvent extends ReadingPageEvent {} | ||
| 50 | + | ||
| 48 | ///音频播放状态 | 51 | ///音频播放状态 |
| 49 | class VoicePlayStateChangeEvent extends ReadingPageEvent {} | 52 | class VoicePlayStateChangeEvent extends ReadingPageEvent {} |
| 50 | 53 |
lib/pages/reading/bloc/reading_state.dart
| @@ -15,3 +15,6 @@ class RequestDataState extends ReadingPageState {} | @@ -15,3 +15,6 @@ class RequestDataState extends ReadingPageState {} | ||
| 15 | class XSVoiceTestState extends ReadingPageState {} | 15 | class XSVoiceTestState extends ReadingPageState {} |
| 16 | 16 | ||
| 17 | class VoicePlayStateChange extends ReadingPageState {} | 17 | class VoicePlayStateChange extends ReadingPageState {} |
| 18 | + | ||
| 19 | +///评测结束反馈弹窗 | ||
| 20 | +class FeedbackState extends ReadingPageState {} |
lib/pages/reading/reading_page.dart
| @@ -3,9 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | @@ -3,9 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| 4 | import 'package:wow_english/common/extension/string_extension.dart'; | 4 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 5 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | 5 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; |
| 6 | +import 'package:wow_english/pages/reading/widgets/reading_dialog_widget.dart'; | ||
| 6 | 7 | ||
| 7 | import '../../common/core/user_util.dart'; | 8 | import '../../common/core/user_util.dart'; |
| 8 | import '../../models/course_process_entity.dart'; | 9 | import '../../models/course_process_entity.dart'; |
| 10 | +import '../../utils/log_util.dart'; | ||
| 9 | import 'bloc/reading_bloc.dart'; | 11 | import 'bloc/reading_bloc.dart'; |
| 10 | 12 | ||
| 11 | class ReadingPage extends StatelessWidget { | 13 | class ReadingPage extends StatelessWidget { |
| @@ -29,21 +31,26 @@ class _ReadingPage extends StatelessWidget { | @@ -29,21 +31,26 @@ class _ReadingPage extends StatelessWidget { | ||
| 29 | Widget build(BuildContext context) { | 31 | Widget build(BuildContext context) { |
| 30 | return BlocListener<ReadingPageBloc, ReadingPageState>( | 32 | return BlocListener<ReadingPageBloc, ReadingPageState>( |
| 31 | listener: (context, state) { | 33 | listener: (context, state) { |
| 34 | + Log.d('reading BlocListener=$state'); | ||
| 32 | if (state is RequestDataState) { | 35 | if (state is RequestDataState) { |
| 33 | - // context.read<TopicPictureBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
| 34 | - print('reading RequestDataState=$state'); | ||
| 35 | - | ||
| 36 | ///刷新页面 | 36 | ///刷新页面 |
| 37 | context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); | 37 | context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); |
| 38 | } | 38 | } |
| 39 | + if (state is FeedbackState) { | ||
| 40 | + showDialog<ReadingDialog>( | ||
| 41 | + context: context, | ||
| 42 | + barrierDismissible: false, | ||
| 43 | + builder: (context) { | ||
| 44 | + return const ReadingDialog(); | ||
| 45 | + }); | ||
| 46 | + } | ||
| 39 | }, | 47 | }, |
| 40 | child: _readingPageView(), | 48 | child: _readingPageView(), |
| 41 | ); | 49 | ); |
| 42 | } | 50 | } |
| 43 | 51 | ||
| 44 | Widget _readingPageView() => BlocBuilder<ReadingPageBloc, ReadingPageState>( | 52 | Widget _readingPageView() => BlocBuilder<ReadingPageBloc, ReadingPageState>( |
| 45 | - buildWhen: (_, s) => s is CurrentPageIndexState, | ||
| 46 | - builder: (context, state) { | 53 | + builder: (context, state) { |
| 47 | final bloc = BlocProvider.of<ReadingPageBloc>(context); | 54 | final bloc = BlocProvider.of<ReadingPageBloc>(context); |
| 48 | return Container( | 55 | return Container( |
| 49 | color: Colors.white, | 56 | color: Colors.white, |
| @@ -176,7 +183,9 @@ class _ReadingPage extends StatelessWidget { | @@ -176,7 +183,9 @@ class _ReadingPage extends StatelessWidget { | ||
| 176 | } | 183 | } |
| 177 | }, | 184 | }, |
| 178 | child: Image.asset( | 185 | child: Image.asset( |
| 179 | - 'micro_phone'.assetPng, | 186 | + bloc.isRecording |
| 187 | + ? 'micro_phone'.assetGif | ||
| 188 | + : 'micro_phone'.assetPng, | ||
| 180 | height: 47.h, | 189 | height: 47.h, |
| 181 | width: 47.w, | 190 | width: 47.w, |
| 182 | )), | 191 | )), |
| @@ -213,8 +222,10 @@ class _ReadingPage extends StatelessWidget { | @@ -213,8 +222,10 @@ class _ReadingPage extends StatelessWidget { | ||
| 213 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { | 222 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { |
| 214 | return Stack( | 223 | return Stack( |
| 215 | children: [ | 224 | children: [ |
| 216 | - Image.network(readings.picUrl ?? '', | ||
| 217 | - height: double.infinity, width: double.infinity), | 225 | + Positioned.fill( |
| 226 | + child: | ||
| 227 | + Image.network(readings.picUrl ?? '', fit: BoxFit.cover), | ||
| 228 | + ), | ||
| 218 | ], | 229 | ], |
| 219 | ); | 230 | ); |
| 220 | }); | 231 | }); |
lib/pages/reading/widgets/reading_dialog_widget.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 3 | +import 'package:flutter/material.dart'; | ||
| 4 | +import 'package:flutter_screenutil/flutter_screenutil.dart'; | ||
| 5 | +import 'package:wow_english/common/extension/string_extension.dart'; | ||
| 6 | + | ||
| 7 | +///评测结束反馈弹窗 | ||
| 8 | +class ReadingDialog extends Dialog { | ||
| 9 | + | ||
| 10 | + const ReadingDialog({super.key}); | ||
| 11 | + | ||
| 12 | + //定时器,自动关闭Diolog | ||
| 13 | + _showTimer(context) { | ||
| 14 | + Timer.periodic(const Duration(milliseconds: 2000), //2000毫秒就是三秒 | ||
| 15 | + (t) { | ||
| 16 | + Navigator.pop(context); | ||
| 17 | + t.cancel(); //取消定时器 timer.cancel(); | ||
| 18 | + }); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + @override | ||
| 22 | + Widget build(BuildContext context) { | ||
| 23 | + _showTimer(context); | ||
| 24 | + return Material( | ||
| 25 | + type: MaterialType.transparency, | ||
| 26 | + child: Center( | ||
| 27 | + child: Container( | ||
| 28 | + width: 250, | ||
| 29 | + height: double.infinity, | ||
| 30 | + color: Colors.transparent, | ||
| 31 | + child: Column( | ||
| 32 | + crossAxisAlignment: CrossAxisAlignment.center, | ||
| 33 | + mainAxisAlignment: MainAxisAlignment.center, | ||
| 34 | + children: [ | ||
| 35 | + Image.asset( | ||
| 36 | + 'text_very_good'.assetWebp, | ||
| 37 | + width: 237.w, | ||
| 38 | + height: 42.h, | ||
| 39 | + ), | ||
| 40 | + Image.asset( | ||
| 41 | + 'pic_very_good'.assetWebp, | ||
| 42 | + width: 210.w, | ||
| 43 | + height: 228.h, | ||
| 44 | + ), | ||
| 45 | + ], | ||
| 46 | + ), | ||
| 47 | + ), | ||
| 48 | + ), | ||
| 49 | + ); | ||
| 50 | + } | ||
| 51 | +} |