Commit a506beff7f4ef20d2016b98e901c8aeda51af822
1 parent
608c05b4
feat:先声sdk方法找不到问题修复;绘本接口&逻辑
Showing
10 changed files
with
269 additions
and
81 deletions
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt
| @@ -16,6 +16,8 @@ class MainActivity : FlutterActivity() { | @@ -16,6 +16,8 @@ class MainActivity : FlutterActivity() { | ||
| 16 | override fun onCreate(savedInstanceState: Bundle?) { | 16 | override fun onCreate(savedInstanceState: Bundle?) { |
| 17 | super.onCreate(savedInstanceState) | 17 | super.onCreate(savedInstanceState) |
| 18 | Log.i("WowEnglish", "MainActivity onCreate") | 18 | Log.i("WowEnglish", "MainActivity onCreate") |
| 19 | + | ||
| 20 | + flutterEngine?.let { SingSoungMethodChannel(this, it) } | ||
| 19 | } | 21 | } |
| 20 | 22 | ||
| 21 | override fun onResume() { | 23 | override fun onResume() { |
| @@ -46,9 +48,4 @@ class MainActivity : FlutterActivity() { | @@ -46,9 +48,4 @@ class MainActivity : FlutterActivity() { | ||
| 46 | // 打开沉浸式 | 48 | // 打开沉浸式 |
| 47 | WindowCompat.setDecorFitsSystemWindows(window, false)*/ | 49 | WindowCompat.setDecorFitsSystemWindows(window, false)*/ |
| 48 | } | 50 | } |
| 49 | - | ||
| 50 | - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | ||
| 51 | - super.configureFlutterEngine(flutterEngine) | ||
| 52 | - SingSoungMethodChannel(this, flutterEngine) | ||
| 53 | - } | ||
| 54 | } | 51 | } |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
| 1 | package com.kouyuxingqiu.wow_english.methodChannels | 1 | package com.kouyuxingqiu.wow_english.methodChannels |
| 2 | 2 | ||
| 3 | +import android.util.Log | ||
| 4 | +import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper | ||
| 3 | import io.flutter.embedding.android.FlutterActivity | 5 | import io.flutter.embedding.android.FlutterActivity |
| 4 | import io.flutter.embedding.engine.FlutterEngine | 6 | import io.flutter.embedding.engine.FlutterEngine |
| 5 | import io.flutter.plugin.common.MethodChannel | 7 | import io.flutter.plugin.common.MethodChannel |
| @@ -30,10 +32,18 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F | @@ -30,10 +32,18 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F | ||
| 30 | ) | 32 | ) |
| 31 | methodChannel?.setMethodCallHandler { call, result -> | 33 | methodChannel?.setMethodCallHandler { call, result -> |
| 32 | when (call.method) { | 34 | when (call.method) { |
| 33 | - "startRecord" -> { | ||
| 34 | - val jsonStr = call.arguments as? String ?: return@setMethodCallHandler | 35 | + "initVoiceSdk" -> { |
| 36 | + SingEngineHelper.init(activity) | ||
| 37 | + } | ||
| 38 | + "startVoice" -> { | ||
| 39 | + val paramMap = call.arguments as HashMap<String, String> | ||
| 40 | + Log.d("WQF", "SingSoungMethodChannel startVoice=${call.arguments.javaClass} paramMap=$paramMap") | ||
| 41 | + paramMap["word"]?.let { SingEngineHelper.startRecord(it) } | ||
| 35 | //do nothing | 42 | //do nothing |
| 36 | } | 43 | } |
| 44 | + "stopVoice" -> { | ||
| 45 | + Log.d("WQF", "SingSoungMethodChannel stopVoice") | ||
| 46 | + } | ||
| 37 | else -> { | 47 | else -> { |
| 38 | result.notImplemented() | 48 | result.notImplemented() |
| 39 | } | 49 | } |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt
| @@ -18,7 +18,7 @@ import org.json.JSONObject | @@ -18,7 +18,7 @@ import org.json.JSONObject | ||
| 18 | import java.util.* | 18 | import java.util.* |
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | -class SingEngineHelper private constructor() : | 21 | +object SingEngineHelper : |
| 22 | AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener { | 22 | AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener { |
| 23 | 23 | ||
| 24 | private val TAG = "SingEngineManager" | 24 | private val TAG = "SingEngineManager" |
| @@ -54,57 +54,59 @@ class SingEngineHelper private constructor() : | @@ -54,57 +54,59 @@ class SingEngineHelper private constructor() : | ||
| 54 | mListeners = mutableListOf() | 54 | mListeners = mutableListOf() |
| 55 | if (mSingEngine == null) { | 55 | if (mSingEngine == null) { |
| 56 | mSingEngine = SingEngine.newInstance(context) | 56 | mSingEngine = SingEngine.newInstance(context) |
| 57 | - } | ||
| 58 | - Thread { | ||
| 59 | - try { | ||
| 60 | - mSingEngine?.run { | ||
| 61 | - // 设置测评结果监听器 | ||
| 62 | - setListener(this@SingEngineHelper) | ||
| 63 | - // 设置录音器初始化错误的回调 | ||
| 64 | - setAudioErrorCallback(this@SingEngineHelper) | ||
| 65 | - setEvalReturnRequestIdCallback(this@SingEngineHelper) | 57 | + Thread { |
| 58 | + try { | ||
| 59 | + mSingEngine?.run { | ||
| 60 | + // 设置测评结果监听器 | ||
| 61 | + setListener(this@SingEngineHelper) | ||
| 62 | + // 设置录音器初始化错误的回调 | ||
| 63 | + setAudioErrorCallback(this@SingEngineHelper) | ||
| 64 | + setEvalReturnRequestIdCallback(this@SingEngineHelper) | ||
| 66 | // // 设置音频格式 | 65 | // // 设置音频格式 |
| 67 | // setAudioType(AudioTypeEnum.WAV) | 66 | // setAudioType(AudioTypeEnum.WAV) |
| 68 | - // 设置引擎类型。引擎类型(在线CLOUD、 离线NATIVE、混合AUTO),默认使用在线引擎。 | ||
| 69 | - setServerType(CoreProvideTypeEnum.CLOUD) | ||
| 70 | - // 设置log日志级别 | ||
| 71 | - setLogLevel(4) | ||
| 72 | - // 禁用实时音量返回 | ||
| 73 | - disableVolume() | ||
| 74 | - // 设置录音音频路径 | ||
| 75 | - wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/" | ||
| 76 | - // 设置是否开启 VAD 功能 | ||
| 77 | - setOpenVad(true, "vad.0.1.bin") | ||
| 78 | - //setOpenVad(false, null); | ||
| 79 | - // 设置 VAD 前置超时时间 | ||
| 80 | - setFrontVadTime(3000) | 67 | + // 设置引擎类型。引擎类型(在线CLOUD、 离线NATIVE、混合AUTO),默认使用在线引擎。 |
| 68 | + setServerType(CoreProvideTypeEnum.CLOUD) | ||
| 69 | + // 设置log日志级别 | ||
| 70 | + setLogLevel(4) | ||
| 71 | + // 禁用实时音量返回 | ||
| 72 | + disableVolume() | ||
| 73 | + // 设置录音音频路径 | ||
| 74 | + wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/" | ||
| 75 | + // 设置是否开启 VAD 功能 | ||
| 76 | + setOpenVad(true, "vad.0.1.bin") | ||
| 77 | + //setOpenVad(false, null); | ||
| 78 | + // 设置 VAD 前置超时时间 | ||
| 79 | + setFrontVadTime(3000) | ||
| 81 | // setServerTimeout(10000) | 80 | // setServerTimeout(10000) |
| 82 | - // 开启错误日志保存到本地,发生错误时文件中会保存到android/data/包名/files/SSError.txt中 | 81 | + // 开启错误日志保存到本地,发生错误时文件中会保存到android/data/包名/files/SSError.txt中 |
| 83 | // setOpenWriteLog(true) | 82 | // setOpenWriteLog(true) |
| 84 | - // 设置在线服务器地址和账号 | ||
| 85 | - setServerAPI("wss://api.cloud.ssapi.cn") | 83 | + // 设置在线服务器地址和账号 |
| 84 | + setServerAPI("wss://api.cloud.ssapi.cn") | ||
| 86 | // // 设置评测语言(针对离线评测) | 85 | // // 设置评测语言(针对离线评测) |
| 87 | // setOffLineSource(OffLineSourceEnum.SOURCE_EN) | 86 | // setOffLineSource(OffLineSourceEnum.SOURCE_EN) |
| 88 | - // 设置引擎初始化参数 | ||
| 89 | - setNewCfg( | ||
| 90 | - buildInitJson( | ||
| 91 | - SingSoundConfig.APPKEY, | ||
| 92 | - SingSoundConfig.SECERTKEY | 87 | + // 设置引擎初始化参数 |
| 88 | + setNewCfg( | ||
| 89 | + buildInitJson( | ||
| 90 | + SingSoundConfig.APPKEY, | ||
| 91 | + SingSoundConfig.SECERTKEY | ||
| 92 | + ) | ||
| 93 | ) | 93 | ) |
| 94 | - ) | ||
| 95 | - // 引擎初始化 | ||
| 96 | - createEngine() | ||
| 97 | - } | 94 | + // 引擎初始化 |
| 95 | + createEngine("1") | ||
| 98 | 96 | ||
| 99 | - getSymbolsMap() | ||
| 100 | - } catch (e: Exception) { | ||
| 101 | - e.printStackTrace() | ||
| 102 | - } | ||
| 103 | - }.start() | 97 | + Log.w(TAG, "createEngine") |
| 98 | + } | ||
| 99 | + | ||
| 100 | + getSymbolsMap() | ||
| 101 | + } catch (e: Exception) { | ||
| 102 | + e.printStackTrace() | ||
| 103 | + } | ||
| 104 | + }.start() | ||
| 105 | + } | ||
| 104 | } | 106 | } |
| 105 | 107 | ||
| 106 | // 开始语音评测 | 108 | // 开始语音评测 |
| 107 | - fun startRecord(originText: String, @EvalTargetType evalTargetType: Int?) { | 109 | + fun startRecord(originText: String, @EvalTargetType evalTargetType: Int? = EvalTargetType.SENTENCE) { |
| 108 | try { | 110 | try { |
| 109 | val request = JSONObject() | 111 | val request = JSONObject() |
| 110 | when (evalTargetType) { | 112 | when (evalTargetType) { |
lib/generated/json/course_process_entity.g.dart
| @@ -38,9 +38,9 @@ Map<String, dynamic> $CourseProcessEntityToJson(CourseProcessEntity entity) { | @@ -38,9 +38,9 @@ Map<String, dynamic> $CourseProcessEntityToJson(CourseProcessEntity entity) { | ||
| 38 | 38 | ||
| 39 | CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) { | 39 | CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) { |
| 40 | final CourseProcessReadings courseProcessReadings = CourseProcessReadings(); | 40 | final CourseProcessReadings courseProcessReadings = CourseProcessReadings(); |
| 41 | - final String? auditUrl = jsonConvert.convert<String>(json['auditUrl']); | ||
| 42 | - if (auditUrl != null) { | ||
| 43 | - courseProcessReadings.auditUrl = auditUrl; | 41 | + final String? audioUrl = jsonConvert.convert<String>(json['audioUrl']); |
| 42 | + if (audioUrl != null) { | ||
| 43 | + courseProcessReadings.audioUrl = audioUrl; | ||
| 44 | } | 44 | } |
| 45 | final int? courseLessonId = jsonConvert.convert<int>(json['courseLessonId']); | 45 | final int? courseLessonId = jsonConvert.convert<int>(json['courseLessonId']); |
| 46 | if (courseLessonId != null) { | 46 | if (courseLessonId != null) { |
| @@ -83,7 +83,7 @@ CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) | @@ -83,7 +83,7 @@ CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) | ||
| 83 | 83 | ||
| 84 | Map<String, dynamic> $CourseProcessReadingsToJson(CourseProcessReadings entity) { | 84 | Map<String, dynamic> $CourseProcessReadingsToJson(CourseProcessReadings entity) { |
| 85 | final Map<String, dynamic> data = <String, dynamic>{}; | 85 | final Map<String, dynamic> data = <String, dynamic>{}; |
| 86 | - data['auditUrl'] = entity.auditUrl; | 86 | + data['audioUrl'] = entity.audioUrl; |
| 87 | data['courseLessonId'] = entity.courseLessonId; | 87 | data['courseLessonId'] = entity.courseLessonId; |
| 88 | data['createTime'] = entity.createTime; | 88 | data['createTime'] = entity.createTime; |
| 89 | data['deleted'] = entity.deleted; | 89 | data['deleted'] = entity.deleted; |
lib/models/course_process_entity.dart
| @@ -24,7 +24,7 @@ class CourseProcessEntity { | @@ -24,7 +24,7 @@ class CourseProcessEntity { | ||
| 24 | 24 | ||
| 25 | @JsonSerializable() | 25 | @JsonSerializable() |
| 26 | class CourseProcessReadings { | 26 | class CourseProcessReadings { |
| 27 | - String? auditUrl; | 27 | + String? audioUrl; |
| 28 | int? courseLessonId; | 28 | int? courseLessonId; |
| 29 | String? createTime; | 29 | String? createTime; |
| 30 | String? deleted; | 30 | String? deleted; |
lib/pages/practice/bloc/topic_picture_bloc.dart
| @@ -142,7 +142,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { | @@ -142,7 +142,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { | ||
| 142 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<TopicPictureState> emitter) async { | 142 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<TopicPictureState> emitter) async { |
| 143 | EasyLoading.show(status: '录音中....'); | 143 | EasyLoading.show(status: '录音中....'); |
| 144 | methodChannel.invokeMethod( | 144 | methodChannel.invokeMethod( |
| 145 | - 'starVoice', | 145 | + 'startVoice', |
| 146 | {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} | 146 | {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} |
| 147 | ); | 147 | ); |
| 148 | _isVoicing = true; | 148 | _isVoicing = true; |
lib/pages/reading/bloc/reading_bloc.dart
| 1 | +import 'package:audioplayers/audioplayers.dart'; | ||
| 1 | import 'package:flutter/cupertino.dart'; | 2 | import 'package:flutter/cupertino.dart'; |
| 3 | +import 'package:flutter/foundation.dart'; | ||
| 4 | +import 'package:flutter/services.dart'; | ||
| 2 | import 'package:flutter_bloc/flutter_bloc.dart'; | 5 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 6 | +import 'package:flutter_easyloading/flutter_easyloading.dart'; | ||
| 3 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | 7 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; |
| 4 | 8 | ||
| 9 | +import '../../../common/request/dao/listen_dao.dart'; | ||
| 10 | +import '../../../common/request/exception.dart'; | ||
| 11 | +import '../../../models/course_process_entity.dart'; | ||
| 12 | +import '../../../utils/loading.dart'; | ||
| 13 | + | ||
| 5 | part 'reading_event.dart'; | 14 | part 'reading_event.dart'; |
| 6 | part 'reading_state.dart'; | 15 | part 'reading_state.dart'; |
| 7 | 16 | ||
| 8 | class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | 17 | class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
| 9 | - | ||
| 10 | final PageController pageController; | 18 | final PageController pageController; |
| 11 | 19 | ||
| 12 | ///当前页索引 | 20 | ///当前页索引 |
| @@ -19,26 +27,68 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -19,26 +27,68 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 19 | 27 | ||
| 20 | ReadingModeType get currentMode => _currentMode; | 28 | ReadingModeType get currentMode => _currentMode; |
| 21 | 29 | ||
| 30 | + CourseProcessEntity? _entity; | ||
| 31 | + | ||
| 32 | + CourseProcessEntity? get entity => _entity; | ||
| 33 | + | ||
| 34 | + ///正在评测 | ||
| 35 | + bool _isRecording = false; | ||
| 36 | + | ||
| 37 | + bool get isRecording => _isRecording; | ||
| 38 | + | ||
| 39 | + late MethodChannel methodChannel; | ||
| 40 | + | ||
| 41 | + late AudioPlayer audioPlayer; | ||
| 42 | + | ||
| 22 | ReadingPageBloc(this.pageController) : super(ReadingPageInitial()) { | 43 | ReadingPageBloc(this.pageController) : super(ReadingPageInitial()) { |
| 23 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); | 44 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); |
| 24 | on<CurrentModeChangeEvent>(_selectItemLoad); | 45 | on<CurrentModeChangeEvent>(_selectItemLoad); |
| 25 | // pageController.addListener(() { | 46 | // pageController.addListener(() { |
| 26 | // _currentPage = pageController.page!.round(); | 47 | // _currentPage = pageController.page!.round(); |
| 27 | // }); | 48 | // }); |
| 49 | + on<RequestDataEvent>(_requestData); | ||
| 50 | + on<ReadingPageEvent>((event, emit) { | ||
| 51 | + //音频播放器 | ||
| 52 | + audioPlayer = AudioPlayer(); | ||
| 53 | + audioPlayer.onPlayerStateChanged.listen((event) { | ||
| 54 | + if (event == PlayerState.completed) { | ||
| 55 | + if (kDebugMode) { | ||
| 56 | + print('绘本播放完成'); | ||
| 57 | + | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + }); | ||
| 61 | + | ||
| 62 | + methodChannel = const MethodChannel('sing_sound_method_channel'); | ||
| 63 | + methodChannel.invokeMethod('initVoiceSdk',{}); | ||
| 64 | + methodChannel.setMethodCallHandler((call) async { | ||
| 65 | + if (call.method == 'voiceResult') {//评测结束 | ||
| 66 | + // add(XSVoiceResultEvent(call.arguments)); | ||
| 67 | + } | ||
| 68 | + }); | ||
| 69 | + }); | ||
| 70 | + on<PlayOriginalAudioEvent>(_playOriginalAudio); | ||
| 71 | + on<XSVoiceTestEvent>(_voiceXsTest); | ||
| 72 | + on<XSVoiceResultEvent>(_voiceXsResult); | ||
| 28 | } | 73 | } |
| 29 | 74 | ||
| 30 | @override | 75 | @override |
| 31 | Future<void> close() { | 76 | Future<void> close() { |
| 32 | pageController.dispose(); | 77 | pageController.dispose(); |
| 78 | + audioPlayer.release(); | ||
| 79 | + audioPlayer.dispose(); | ||
| 33 | return super.close(); | 80 | return super.close(); |
| 34 | } | 81 | } |
| 35 | 82 | ||
| 36 | - void _pageControllerChange(CurrentPageIndexChangeEvent event, Emitter<ReadingPageState> emitter) async { | 83 | + void _pageControllerChange(CurrentPageIndexChangeEvent event, |
| 84 | + Emitter<ReadingPageState> emitter) async { | ||
| 37 | _currentPage = event.pageIndex; | 85 | _currentPage = event.pageIndex; |
| 86 | + _playOriginVoice(null); | ||
| 38 | emitter(CurrentPageIndexState()); | 87 | emitter(CurrentPageIndexState()); |
| 39 | } | 88 | } |
| 40 | 89 | ||
| 41 | - void _selectItemLoad(CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async { | 90 | + void _selectItemLoad( |
| 91 | + CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async { | ||
| 42 | if (_currentMode == ReadingModeType.auto) { | 92 | if (_currentMode == ReadingModeType.auto) { |
| 43 | _currentMode = ReadingModeType.manual; | 93 | _currentMode = ReadingModeType.manual; |
| 44 | } else { | 94 | } else { |
| @@ -46,4 +96,77 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -46,4 +96,77 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
| 46 | } | 96 | } |
| 47 | emitter(CurrentModeState()); | 97 | emitter(CurrentModeState()); |
| 48 | } | 98 | } |
| 99 | + | ||
| 100 | + ///请求数据 | ||
| 101 | + void _requestData( | ||
| 102 | + RequestDataEvent event, Emitter<ReadingPageState> emitter) async { | ||
| 103 | + try { | ||
| 104 | + await loading(() async { | ||
| 105 | + _entity = await ListenDao.process('1'); | ||
| 106 | + print("reading page entity: ${_entity!.toJson()}"); | ||
| 107 | + emitter(RequestDataState()); | ||
| 108 | + }); | ||
| 109 | + } catch (e) { | ||
| 110 | + if (e is ApiException) { | ||
| 111 | + EasyLoading.showToast(e.message ?? '请求失败,请检查网络连接'); | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + void _playOriginalAudio(PlayOriginalAudioEvent event, Emitter<ReadingPageState> emitter) async { | ||
| 117 | + print("_playOriginalAudio"); | ||
| 118 | + _playOriginVoice(event.url); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + /// 播放绘本原音 | ||
| 122 | + void _playOriginVoice(String? audioUrl) async { | ||
| 123 | + audioPlayer.stop(); | ||
| 124 | + final readingData = currentPageData(); | ||
| 125 | + if (readingData?.audioUrl != null) { | ||
| 126 | + final urlStr = audioUrl ?? readingData?.audioUrl ?? ''; | ||
| 127 | + if (urlStr.isNotEmpty) { | ||
| 128 | + audioPlayer.play(UrlSource(urlStr)); | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + int dataCount() { | ||
| 134 | + // print("dataCount=${_entity?.readings?.length ?? 0}"); | ||
| 135 | + return _entity?.readings?.length ?? 0; | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + CourseProcessReadings? currentPageData() { | ||
| 139 | + return _entity?.readings?[_currentPage]; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + ///先声测试 | ||
| 143 | + void _voiceXsTest(XSVoiceTestEvent event, Emitter<ReadingPageState> emitter) async { | ||
| 144 | + startRecord(event.content); | ||
| 145 | + emitter(XSVoiceTestState()); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + void startRecord(String content) async { | ||
| 149 | + if (_isRecording == true) { | ||
| 150 | + return; | ||
| 151 | + } | ||
| 152 | + EasyLoading.show(status: '录音中....'); | ||
| 153 | + methodChannel.invokeMethod( | ||
| 154 | + 'startVoice', | ||
| 155 | + {'word':'how old are you','type':'0','userId':'1'} | ||
| 156 | + ); | ||
| 157 | + _isRecording = true; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + void _voiceXsResult(XSVoiceResultEvent event,Emitter<ReadingPageState> emitter) async { | ||
| 161 | + final Map args = event.message as Map; | ||
| 162 | + final result = args['result'] as String; | ||
| 163 | + if (result == '1') { | ||
| 164 | + final overall = args['overall'].toString(); | ||
| 165 | + EasyLoading.showToast('测评成功,分数是$overall',duration: const Duration(seconds: 10)); | ||
| 166 | + } else { | ||
| 167 | + EasyLoading.showToast('测评失败',duration: const Duration(seconds: 10)); | ||
| 168 | + } | ||
| 169 | + _isRecording = false; | ||
| 170 | + emitter(XSVoiceTestState()); | ||
| 171 | + } | ||
| 49 | } | 172 | } |
lib/pages/reading/bloc/reading_event.dart
| @@ -8,4 +8,33 @@ class CurrentPageIndexChangeEvent extends ReadingPageEvent { | @@ -8,4 +8,33 @@ class CurrentPageIndexChangeEvent extends ReadingPageEvent { | ||
| 8 | CurrentPageIndexChangeEvent(this.pageIndex); | 8 | CurrentPageIndexChangeEvent(this.pageIndex); |
| 9 | } | 9 | } |
| 10 | 10 | ||
| 11 | -class CurrentModeChangeEvent extends ReadingPageEvent {} | ||
| 12 | \ No newline at end of file | 11 | \ No newline at end of file |
| 12 | +class CurrentModeChangeEvent extends ReadingPageEvent {} | ||
| 13 | + | ||
| 14 | +///请求接口获取数据 | ||
| 15 | +class RequestDataEvent extends ReadingPageEvent {} | ||
| 16 | + | ||
| 17 | +///播放原音频 | ||
| 18 | +class PlayOriginalAudioEvent extends ReadingPageEvent { | ||
| 19 | + final String? url; | ||
| 20 | + PlayOriginalAudioEvent(this.url); | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +///初始化先声SDK | ||
| 24 | +class XSVoiceInitEvent extends ReadingPageEvent { | ||
| 25 | + final Map data; | ||
| 26 | + XSVoiceInitEvent(this.data); | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +///评测结果 | ||
| 30 | +class XSVoiceResultEvent extends ReadingPageEvent { | ||
| 31 | + final dynamic message; | ||
| 32 | + XSVoiceResultEvent(this.message); | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +///先声测试 | ||
| 36 | +class XSVoiceTestEvent extends ReadingPageEvent { | ||
| 37 | + final String content; | ||
| 38 | + final String type; | ||
| 39 | + final String userId; | ||
| 40 | + XSVoiceTestEvent(this.content,this.type,this.userId); | ||
| 41 | +} | ||
| 13 | \ No newline at end of file | 42 | \ No newline at end of file |
lib/pages/reading/bloc/reading_state.dart
| @@ -9,3 +9,7 @@ class CurrentPageIndexState extends ReadingPageState {} | @@ -9,3 +9,7 @@ class CurrentPageIndexState extends ReadingPageState {} | ||
| 9 | 9 | ||
| 10 | /// 手动or自动播放 | 10 | /// 手动or自动播放 |
| 11 | class CurrentModeState extends ReadingPageState {} | 11 | class CurrentModeState extends ReadingPageState {} |
| 12 | + | ||
| 13 | +class RequestDataState extends ReadingPageState {} | ||
| 14 | + | ||
| 15 | +class XSVoiceTestState extends ReadingPageState {} |
lib/pages/reading/reading_page.dart
| @@ -4,6 +4,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; | @@ -4,6 +4,8 @@ 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 | 6 | ||
| 7 | +import '../../common/core/user_util.dart'; | ||
| 8 | +import '../../models/course_process_entity.dart'; | ||
| 7 | import 'bloc/reading_bloc.dart'; | 9 | import 'bloc/reading_bloc.dart'; |
| 8 | 10 | ||
| 9 | class ReadingPage extends StatelessWidget { | 11 | class ReadingPage extends StatelessWidget { |
| @@ -12,7 +14,7 @@ class ReadingPage extends StatelessWidget { | @@ -12,7 +14,7 @@ class ReadingPage extends StatelessWidget { | ||
| 12 | @override | 14 | @override |
| 13 | Widget build(BuildContext context) { | 15 | Widget build(BuildContext context) { |
| 14 | return BlocProvider( | 16 | return BlocProvider( |
| 15 | - create: (_) => ReadingPageBloc(PageController()), | 17 | + create: (_) => ReadingPageBloc(PageController())..add(RequestDataEvent()), |
| 16 | child: _ReadingPage(), | 18 | child: _ReadingPage(), |
| 17 | ); | 19 | ); |
| 18 | } | 20 | } |
| @@ -22,7 +24,15 @@ class _ReadingPage extends StatelessWidget { | @@ -22,7 +24,15 @@ class _ReadingPage extends StatelessWidget { | ||
| 22 | @override | 24 | @override |
| 23 | Widget build(BuildContext context) { | 25 | Widget build(BuildContext context) { |
| 24 | return BlocListener<ReadingPageBloc, ReadingPageState>( | 26 | return BlocListener<ReadingPageBloc, ReadingPageState>( |
| 25 | - listener: (context, state) {}, | 27 | + listener: (context, state) { |
| 28 | + if (state is RequestDataState) { | ||
| 29 | + // context.read<TopicPictureBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
| 30 | + print('reading RequestDataState=$state'); | ||
| 31 | + | ||
| 32 | + ///刷新页面 | ||
| 33 | + context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
| 34 | + } | ||
| 35 | + }, | ||
| 26 | child: _readingPageView(), | 36 | child: _readingPageView(), |
| 27 | ); | 37 | ); |
| 28 | } | 38 | } |
| @@ -36,13 +46,13 @@ class _ReadingPage extends StatelessWidget { | @@ -36,13 +46,13 @@ class _ReadingPage extends StatelessWidget { | ||
| 36 | child: Stack( | 46 | child: Stack( |
| 37 | children: [ | 47 | children: [ |
| 38 | PageView.builder( | 48 | PageView.builder( |
| 39 | - itemCount: 10, | 49 | + itemCount: bloc.dataCount(), |
| 40 | controller: bloc.pageController, | 50 | controller: bloc.pageController, |
| 41 | onPageChanged: (int index) { | 51 | onPageChanged: (int index) { |
| 42 | bloc.add(CurrentPageIndexChangeEvent(index)); | 52 | bloc.add(CurrentPageIndexChangeEvent(index)); |
| 43 | }, | 53 | }, |
| 44 | itemBuilder: (context, int index) { | 54 | itemBuilder: (context, int index) { |
| 45 | - return _readingPagerItem(); | 55 | + return _readingPagerItem(bloc.entity!.readings![index]); |
| 46 | }), | 56 | }), |
| 47 | Container( | 57 | Container( |
| 48 | color: Colors.transparent, | 58 | color: Colors.transparent, |
| @@ -76,15 +86,14 @@ class _ReadingPage extends StatelessWidget { | @@ -76,15 +86,14 @@ class _ReadingPage extends StatelessWidget { | ||
| 76 | ), | 86 | ), |
| 77 | alignment: Alignment.center, | 87 | alignment: Alignment.center, |
| 78 | child: Text( | 88 | child: Text( |
| 79 | - '${bloc.currentPage}/10', | ||
| 80 | - | ||
| 81 | - ///todo 分母需要替换成数据数组长度 | 89 | + '${bloc.currentPage}/${bloc.dataCount()}', |
| 82 | style: TextStyle(fontSize: 20.sp, color: Colors.white), | 90 | style: TextStyle(fontSize: 20.sp, color: Colors.white), |
| 83 | ), | 91 | ), |
| 84 | ), | 92 | ), |
| 85 | 93 | ||
| 86 | Padding( | 94 | Padding( |
| 87 | - padding: EdgeInsets.only(right: 15.w + ScreenUtil().bottomBarHeight), | 95 | + padding: EdgeInsets.only( |
| 96 | + right: 15.w + ScreenUtil().bottomBarHeight), | ||
| 88 | child: GestureDetector( | 97 | child: GestureDetector( |
| 89 | onTap: () { | 98 | onTap: () { |
| 90 | bloc.add(CurrentModeChangeEvent()); | 99 | bloc.add(CurrentModeChangeEvent()); |
| @@ -121,17 +130,26 @@ class _ReadingPage extends StatelessWidget { | @@ -121,17 +130,26 @@ class _ReadingPage extends StatelessWidget { | ||
| 121 | margin: EdgeInsets.symmetric(horizontal: 10.w), | 130 | margin: EdgeInsets.symmetric(horizontal: 10.w), |
| 122 | child: Row( | 131 | child: Row( |
| 123 | children: [ | 132 | children: [ |
| 124 | - Image.asset( | ||
| 125 | - 'voice'.assetPng, | ||
| 126 | - height: 40.h, | ||
| 127 | - width: 45.w, | 133 | + GestureDetector( |
| 134 | + onTap: () { | ||
| 135 | + if (bloc.isRecording) { | ||
| 136 | + return; | ||
| 137 | + } | ||
| 138 | + print("voice tap"); | ||
| 139 | + bloc.add(PlayOriginalAudioEvent(null)); | ||
| 140 | + }, | ||
| 141 | + child: Image.asset( | ||
| 142 | + 'voice'.assetPng, | ||
| 143 | + height: 40.h, | ||
| 144 | + width: 45.w, | ||
| 145 | + ), | ||
| 128 | ), | 146 | ), |
| 129 | SizedBox( | 147 | SizedBox( |
| 130 | width: 10.w, | 148 | width: 10.w, |
| 131 | ), | 149 | ), |
| 132 | Expanded( | 150 | Expanded( |
| 133 | child: Text( | 151 | child: Text( |
| 134 | - "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld", | 152 | + bloc.currentPageData()?.word ?? '', |
| 135 | style: TextStyle( | 153 | style: TextStyle( |
| 136 | color: const Color(0xFF333333), fontSize: 21.sp), | 154 | color: const Color(0xFF333333), fontSize: 21.sp), |
| 137 | maxLines: 2, | 155 | maxLines: 2, |
| @@ -140,11 +158,18 @@ class _ReadingPage extends StatelessWidget { | @@ -140,11 +158,18 @@ class _ReadingPage extends StatelessWidget { | ||
| 140 | SizedBox( | 158 | SizedBox( |
| 141 | width: 10.w, | 159 | width: 10.w, |
| 142 | ), | 160 | ), |
| 143 | - Image.asset( | ||
| 144 | - 'micro_phone'.assetPng, | ||
| 145 | - height: 47.h, | ||
| 146 | - width: 47.w, | ||
| 147 | - ), | 161 | + GestureDetector( |
| 162 | + onTap: () { | ||
| 163 | + if (bloc.isRecording) { | ||
| 164 | + return; | ||
| 165 | + } | ||
| 166 | + bloc.add(XSVoiceTestEvent(bloc.currentPageData()?.word??'', '0',UserUtil.getUser()!.id.toString())); | ||
| 167 | + }, | ||
| 168 | + child: Image.asset( | ||
| 169 | + 'micro_phone'.assetPng, | ||
| 170 | + height: 47.h, | ||
| 171 | + width: 47.w, | ||
| 172 | + )), | ||
| 148 | SizedBox( | 173 | SizedBox( |
| 149 | width: 10.w, | 174 | width: 10.w, |
| 150 | ), | 175 | ), |
| @@ -169,14 +194,12 @@ class _ReadingPage extends StatelessWidget { | @@ -169,14 +194,12 @@ class _ReadingPage extends StatelessWidget { | ||
| 169 | ); | 194 | ); |
| 170 | }); | 195 | }); |
| 171 | 196 | ||
| 172 | - Widget _readingPagerItem() => | 197 | + Widget _readingPagerItem(CourseProcessReadings readings) => |
| 173 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { | 198 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { |
| 174 | return Stack( | 199 | return Stack( |
| 175 | children: [ | 200 | children: [ |
| 176 | - Image.network( | ||
| 177 | - 'https://img.liblibai.com/web/648331d5a2cb5.png?image_process=format,webp&x-oss-process=image/resize,w_2980,m_lfit/format,webp', | ||
| 178 | - height: double.infinity, | ||
| 179 | - width: double.infinity), | 201 | + Image.network(readings.picUrl ?? '', |
| 202 | + height: double.infinity, width: double.infinity), | ||
| 180 | ], | 203 | ], |
| 181 | ); | 204 | ); |
| 182 | }); | 205 | }); |