diff --git a/android/app/build.gradle b/android/app/build.gradle index 92f2904..27c2f34 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -94,5 +94,5 @@ dependencies { // kotlin扩展(可选) implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2' // coco2d游戏 - implementation 'io.keyss.android.library:steve_game:1.0.0' + implementation 'io.keyss.android.library:steve_game:1.0.1' } diff --git a/android/build.gradle b/android/build.gradle index 482dae9..42499bb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,10 @@ buildscript { ext.kotlin_version = '1.8.21' repositories { + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url 'https://jitpack.io' } +// maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } google() mavenCentral() } @@ -14,6 +18,10 @@ buildscript { allprojects { repositories { + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url 'https://jitpack.io' } +// maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } google() mavenCentral() maven { url 'https://repo.singsound.com/repository/singsound_ginger_android_sdk/' } diff --git a/assets/sounds/correct_voice.mp3 b/assets/sounds/correct_voice.mp3 new file mode 100644 index 0000000..6aa429c --- /dev/null +++ b/assets/sounds/correct_voice.mp3 diff --git a/assets/sounds/incorrect_voice.mp3 b/assets/sounds/incorrect_voice.mp3 new file mode 100644 index 0000000..734ba1d --- /dev/null +++ b/assets/sounds/incorrect_voice.mp3 diff --git a/lib/common/core/app_config_helper.dart b/lib/common/core/app_config_helper.dart index df3ea0f..45df42b 100644 --- a/lib/common/core/app_config_helper.dart +++ b/lib/common/core/app_config_helper.dart @@ -22,8 +22,8 @@ class AppConfigHelper { static bool checkedUpdate = false; // 获取用户信息 - static Future getAppConfig() async { - if (configEntityEntity != null) { + static Future getAppConfig({ bool forceSync = false }) async { + if (forceSync == false && configEntityEntity != null) { return configEntityEntity; } configEntityEntity = await SystemDao.getAppConfig(); diff --git a/lib/common/core/user_util.dart b/lib/common/core/user_util.dart index c990c45..018f15f 100644 --- a/lib/common/core/user_util.dart +++ b/lib/common/core/user_util.dart @@ -63,8 +63,8 @@ class UserUtil { pushNamedAndRemoveUntil(AppRouteName.login, (route) => false, arguments: {'showPasswordPage': showPasswordLoginPage}); } - // 是否有游戏权限 - static bool hasGamePermission() { + // 是否有权限 + static bool hasPermission() { return _userEntity?.valid ?? false; } diff --git a/lib/common/extension/string_extension.dart b/lib/common/extension/string_extension.dart index d5462e0..c0e7c38 100644 --- a/lib/common/extension/string_extension.dart +++ b/lib/common/extension/string_extension.dart @@ -1,6 +1,7 @@ /// 资源类扩展方法 extension AssetExtension on String { static const String _assetImagePrefix = "assets/images/"; + static const String _assetSoundPrefix = 'sounds/'; /// 图片url String get assetImg => _assetImagePrefix + this; @@ -10,6 +11,10 @@ extension AssetExtension on String { String get assetWebp => '$assetImg.webp'; String get assetGif => '$assetImg.gif'; + + String get assetSound => _assetSoundPrefix + this; + + String get assetMp3 => '$assetSound.mp3'; } extension StringExtension on String { diff --git a/lib/common/request/dao/user_dao.dart b/lib/common/request/dao/user_dao.dart index 18bae66..cd6c2c9 100644 --- a/lib/common/request/dao/user_dao.dart +++ b/lib/common/request/dao/user_dao.dart @@ -2,6 +2,7 @@ import 'package:wow_english/common/core/user_util.dart'; import 'package:wow_english/models/user_entity.dart'; import 'package:wow_english/utils/log_util.dart'; +import '../../core/app_config_helper.dart'; import '../request_client.dart'; enum SmsType { login, change_passWord, stdDestroy } @@ -18,6 +19,8 @@ class UserDao { UserUtil.saveUser(data); // 由于userInfo接口不会返回token,所以这里需要再次保存一下token final token = data.token; + //登录成功后刷新下配置信息 + AppConfigHelper.getAppConfig(forceSync: true); //登录成功后zip一下getUserInfo,因为进入首页需要的信息在userinfo里,保证进入首页数据是最新的 data = await getUserInfo(); data?.token = token; diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 4dc098d..7f9b843 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,7 +1,5 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_app_update/azhon_app_update.dart'; import 'package:flutter_app_update/update_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -69,11 +67,9 @@ class _HomePageView extends StatelessWidget { Expanded( child: GestureDetector( onTap: () { - if (UserUtil.isLogined()) { + _checkPermission(() { pushNamed(AppRouteName.courseUnit); - } else { - pushNamed(AppRouteName.login); - } + }); }, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -105,30 +101,9 @@ class _HomePageView extends StatelessWidget { 'WQF ModuleSelectPage BlocBuilder state: $userState'); return GestureDetector( onTap: () { - //如果没登录先登录 - if (UserUtil.isLogined()) { - if (AppConfigHelper - .shouldHidePay()) { - pushNamed(AppRouteName.games); - } else { - if (UserUtil - .hasGamePermission()) { - pushNamed(AppRouteName.games); - } else { - showTwoActionDialog( - '提示', '忽略', '去续费', - '您的课程已到期,请快快续费继续学习吧!', - leftTap: () { - popPage(); - }, rightTap: () { - popPage(); - pushNamed(AppRouteName.shop); - }); - } - } - } else { - pushNamed(AppRouteName.login); - } + _checkPermission(() { + pushNamed(AppRouteName.games); + }); }, child: Column( mainAxisAlignment: MainAxisAlignment @@ -168,6 +143,30 @@ class _HomePageView extends StatelessWidget { }); + _checkPermission(VoidCallback onAllowed) { + if (UserUtil.isLogined()) { + if (AppConfigHelper.shouldHidePay()) { + onAllowed(); + } else { + if (UserUtil.hasPermission()) { + onAllowed(); + } else { + showTwoActionDialog('提示', '忽略', '去续费', + '您的课程已到期,请快快续费继续学习吧!', leftTap: () { + popPage(); + }, rightTap: () { + popPage(); + pushNamed(AppRouteName.shop); + }); + } + } + } else { + //如果没登录先登录 + pushNamed(AppRouteName.login); + } + } + + ///Flutter侧处理升级对话框 ///[forcedUpgrade] 是否强制升级 _showUpdateDialog(BuildContext context, bool forcedUpgrade, diff --git a/lib/pages/home/widgets/BaseHomeHeaderWidget.dart b/lib/pages/home/widgets/BaseHomeHeaderWidget.dart index 28ad771..8d4795b 100644 --- a/lib/pages/home/widgets/BaseHomeHeaderWidget.dart +++ b/lib/pages/home/widgets/BaseHomeHeaderWidget.dart @@ -79,18 +79,24 @@ class BaseHomeHeaderWidget extends StatelessWidget { style: TextStyle(color: Colors.white, fontSize: 30.0), )), Offstage( - offstage: AppConfigHelper.shouldHidePay() || !UserUtil.isLogined(), - child: Row(children: [ - Image( - width: 20.0.w, - height: 20.0.h, - image: AssetImage('ic_countdown'.assetPng)), - // 替换为你的图片资源路径 - const SizedBox(width: 10.0), - // 图片和文本之间的间隔 - Text('还剩${UserUtil.getRemainingValidity()}天'), - ]), - ), + offstage: AppConfigHelper.shouldHidePay() || + !UserUtil.isLogined(), + child: GestureDetector( + onTap: () => { + pushNamed(AppRouteName.shop), + }, + child: Row( + children: [ + Image( + width: 20.0.w, + height: 20.0.h, + image: AssetImage('ic_countdown'.assetPng)), + // 替换为你的图片资源路径 + const SizedBox(width: 6.0), + // 图片和文本之间的间隔 + Text('还剩${UserUtil.getRemainingValidity()}天'), + ]), + )), ScreenUtil().bottomBarHeight.horizontalSpace, ], )); diff --git a/lib/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index 384c477..807cc9c 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/request/dao/listen_dao.dart'; import 'package:wow_english/common/request/exception.dart'; import 'package:wow_english/models/course_process_entity.dart'; @@ -45,6 +46,16 @@ class TopicPictureBloc extends Bloc { ///正在播放音频 VoicePlayState _voicePlayState = VoicePlayState.unKnow; + // 是否是回答(选择)结果音效 + bool _isResultSoundPlaying = false; + + bool get isResultSoundPlaying => _isResultSoundPlaying; + + // 答对播放音效时禁止任何点击(选择)操作 + bool _forbiddenWhenCorrect = false; + + bool get forbiddenWhenCorrect => _forbiddenWhenCorrect; + CourseProcessEntity? get entity => _entity; int get currentPage => _currentPage + 1; @@ -70,29 +81,43 @@ class TopicPictureBloc extends Bloc { on(_requestData); on(_voiceXsTest); on(_voiceXsStop); - on(_voicePlay); + on(_questionVoicePlay); on((event, emit) { //音频播放器 audioPlayer = AudioPlayer(); audioPlayer.onPlayerStateChanged.listen((event) async { - debugPrint('播放状态变化'); - if (event == PlayerState.completed) { - debugPrint('播放完成'); - _voicePlayState = VoicePlayState.completed; - } - if (event == PlayerState.stopped) { - debugPrint('播放结束'); - _voicePlayState = VoicePlayState.stop; - } + debugPrint('播放状态变化 _voicePlayState=$_voicePlayState event=$event _isResultSoundPlaying=$_isResultSoundPlaying _forbiddenWhenCorrect=$_forbiddenWhenCorrect'); + if (_isResultSoundPlaying) { + if (event != PlayerState.playing) { + _isResultSoundPlaying = false; + if (_forbiddenWhenCorrect) { + _forbiddenWhenCorrect = false; + // 答对后自动翻页 + pageController.nextPage( + duration: const Duration(milliseconds: 500), + curve: Curves.ease, + ); + } + } + } else { + if (event == PlayerState.completed) { + debugPrint('播放完成'); + _voicePlayState = VoicePlayState.completed; + } + if (event == PlayerState.stopped) { + debugPrint('播放结束'); + _voicePlayState = VoicePlayState.stop; + } - if (event == PlayerState.playing) { - debugPrint('正在播放中'); - _voicePlayState = VoicePlayState.playing; - } - if(isClosed) { - return; + if (event == PlayerState.playing) { + debugPrint('正在播放中'); + _voicePlayState = VoicePlayState.playing; + } + if (isClosed) { + return; + } + add(VoicePlayStateChangeEvent()); } - add(VoicePlayStateChangeEvent()); }); methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); @@ -129,6 +154,8 @@ class TopicPictureBloc extends Bloc { pageController.dispose(); audioPlayer.release(); audioPlayer.dispose(); + _isResultSoundPlaying = false; + _forbiddenWhenCorrect = false; _voiceXsCancel(); return super.close(); } @@ -149,10 +176,12 @@ class TopicPictureBloc extends Bloc { ///页面切换 void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter emitter) async { - _currentPage = event.pageIndex; - if (voicePlayState == VoicePlayState.playing) { - await audioPlayer.stop(); + await closePlayerResource(); + debugPrint('翻页 $_currentPage->${event.pageIndex}'); + if (_currentPage == _entity?.topics?.length) { + return; } + _currentPage = event.pageIndex; final topics = _entity?.topics?[_currentPage]; if (topics?.type != 3 && topics?.type != 4) { if (topics?.audioUrl != null) { @@ -169,13 +198,18 @@ class TopicPictureBloc extends Bloc { ///选择 void _selectItemLoad(SelectItemEvent event,Emitter emitter) async { + if (_forbiddenWhenCorrect) { + return; + } _selectItem = event.selectIndex; CourseProcessTopics? topics = _entity?.topics?[_currentPage]; CourseProcessTopicsTopicAnswerList? answerList = topics?.topicAnswerList?[_selectItem]; if (answerList?.correct == 0) { - showToast('继续加油哦',duration: const Duration(seconds: 2)); + _playResultSound(false); + // showToast('继续加油哦',duration: const Duration(seconds: 2)); } else { - showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); + _playResultSound(true); + // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); } emitter(SelectItemChangeState()); } @@ -228,17 +262,37 @@ class TopicPictureBloc extends Bloc { emitter(XSVoiceTestState()); } + // 暂时没用上 void _voicePlayStateChange(VoicePlayStateChangeEvent event,Emitter emitter) async { emitter(VoicePlayStateChange()); } - void _voicePlay(VoicePlayEvent event,Emitter emitter) async { - if (voicePlayState == VoicePlayState.playing) { - await audioPlayer.stop(); + // 题目音频播放 + void _questionVoicePlay(VoicePlayEvent event,Emitter emitter) async { + if (_forbiddenWhenCorrect) { return; } + _forbiddenWhenCorrect = false; + await closePlayerResource(); final topics = _entity?.topics?[_currentPage]; final urlStr = topics?.audioUrl??''; await audioPlayer.play(UrlSource(urlStr)); } + + Future closePlayerResource() async { + if (voicePlayState == VoicePlayState.playing || _isResultSoundPlaying) { + await audioPlayer.stop(); + } + } + + void _playResultSound(bool isCorrect) async { + await audioPlayer.stop(); + _isResultSoundPlaying = true; + _forbiddenWhenCorrect = isCorrect; + if (isCorrect) { + await audioPlayer.play(AssetSource('correct_voice'.assetMp3)); + } else { + await audioPlayer.play(AssetSource('incorrect_voice'.assetMp3)); + } + } } diff --git a/lib/pages/practice/bloc/topic_picture_event.dart b/lib/pages/practice/bloc/topic_picture_event.dart index f69ef33..965fed5 100644 --- a/lib/pages/practice/bloc/topic_picture_event.dart +++ b/lib/pages/practice/bloc/topic_picture_event.dart @@ -48,5 +48,5 @@ class SelectItemEvent extends TopicPictureEvent { ///音频播放事件 class VoicePlayChangeEvent extends TopicPictureEvent {} -///播放音乐 +///播放(题目)音乐 class VoicePlayEvent extends TopicPictureEvent {} \ No newline at end of file diff --git a/lib/pages/practice/widgets/practice_header_widget.dart b/lib/pages/practice/widgets/practice_header_widget.dart index 456f314..1d7a091 100644 --- a/lib/pages/practice/widgets/practice_header_widget.dart +++ b/lib/pages/practice/widgets/practice_header_widget.dart @@ -14,46 +14,42 @@ class PracticeHeaderWidget extends StatelessWidget { return Container( color: Colors.white, height: 60.h, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.only( - left: ScreenUtil().bottomBarHeight - ), - child: IconButton( - onPressed: (){ - onTap(); - }, - icon: Image.asset( - 'back_around'.assetPng, - width: 40, - height: 40, - )), + child: AppBar( + leading: IconButton( + icon: Image.asset( + 'back_around'.assetPng, + width: 40, + height: 40, ), - Container( - height: 40.h, - padding: EdgeInsets.symmetric(horizontal: 27.w), - decoration: BoxDecoration( - color: const Color(0xFF00B6F1), - borderRadius: BorderRadius.circular(20.r), - border: Border.all( - width: 1.0, - color: const Color(0xFF333333), - ), - ), - alignment: Alignment.center, - child: Text( - title, - style: TextStyle( - fontSize: 20.sp, - color: Colors.white + onPressed: () { + onTap(); + }, + ), + centerTitle: true, + title: IntrinsicWidth( + child: Container( + height: 40.h, + padding: EdgeInsets.symmetric(horizontal: 27.w), + decoration: BoxDecoration( + color: const Color(0xFF00B6F1), + borderRadius: BorderRadius.circular(20.r), + border: Border.all( + width: 1.0, + color: const Color(0xFF333333), + ), ), - ), + child: Center( + child: Text( + title, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white + ), + ), + ) ), - ScreenUtil().bottomBarHeight.horizontalSpace, - ], - ), + ) + ) ); } } \ No newline at end of file diff --git a/lib/pages/reading/bloc/reading_bloc.dart b/lib/pages/reading/bloc/reading_bloc.dart index e80812a..6dc05d8 100644 --- a/lib/pages/reading/bloc/reading_bloc.dart +++ b/lib/pages/reading/bloc/reading_bloc.dart @@ -309,12 +309,12 @@ class ReadingPageBloc extends Bloc { Log.d("_voiceXsResult result=$result"); final overall = result['overall'].toString(); EasyLoading.showToast('测评成功,分数是$overall', - duration: const Duration(seconds: 10)); + duration: const Duration(seconds: 2)); currentPageData()?.recordScore = overall; currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; ///完成录音后紧接着播放录音 _playRecordAudioInner(); - emitter(FeedbackState()); + // emitter(FeedbackState()); } ///终止评测 diff --git a/lib/pages/section/section_page.dart b/lib/pages/section/section_page.dart index edf88c1..3bb055c 100644 --- a/lib/pages/section/section_page.dart +++ b/lib/pages/section/section_page.dart @@ -183,46 +183,46 @@ class _SectionPageView extends StatelessWidget { ); } })), - SafeArea( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 13.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - height: 47.h, - width: 80.w, - ), - Container( - decoration: BoxDecoration( - color: CourseModuleModel( - bloc.courseUnitEntity.courseModuleCode ?? - 'Phase-1') - .color, - borderRadius: BorderRadius.circular(14.5.r), - ), - padding: EdgeInsets.symmetric( - vertical: 8.h, horizontal: 24.w), - child: Text( - '${(bloc.courseUnitEntity.nowStep ?? 0)}/${bloc.courseUnitEntity.total ?? 0}', - style: TextStyle( - color: Colors.white, fontSize: 12.sp), - ), - ), - Image.asset( - CourseModuleModel( - bloc.courseUnitEntity.courseModuleCode ?? - 'Phase-1') - .courseModuleLogo - .assetPng, - height: 47.h, - width: 80.w, - // color: Colors.red, - ), - ], - ), - ), - ) + // SafeArea( + // child: Padding( + // padding: EdgeInsets.symmetric(horizontal: 13.w), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // SizedBox( + // height: 47.h, + // width: 80.w, + // ), + // Container( + // decoration: BoxDecoration( + // color: CourseModuleModel( + // bloc.courseUnitEntity.courseModuleCode ?? + // 'Phase-1') + // .color, + // borderRadius: BorderRadius.circular(14.5.r), + // ), + // padding: EdgeInsets.symmetric( + // vertical: 8.h, horizontal: 24.w), + // child: Text( + // '${(bloc.courseUnitEntity.nowStep ?? 0)}/${bloc.courseUnitEntity.total ?? 0}', + // style: TextStyle( + // color: Colors.white, fontSize: 12.sp), + // ), + // ), + // Image.asset( + // CourseModuleModel( + // bloc.courseUnitEntity.courseModuleCode ?? + // 'Phase-1') + // .courseModuleLogo + // .assetPng, + // height: 47.h, + // width: 80.w, + // // color: Colors.red, + // ), + // ], + // ), + // ), + // ) ], ), ), diff --git a/lib/pages/shop/home/shop_home_page.dart b/lib/pages/shop/home/shop_home_page.dart index 4c2789e..56f44f7 100644 --- a/lib/pages/shop/home/shop_home_page.dart +++ b/lib/pages/shop/home/shop_home_page.dart @@ -62,14 +62,14 @@ class _ShopHomeView extends StatelessWidget { ), body: Center( child: Padding( - padding: EdgeInsets.symmetric(vertical: 25.h, horizontal: 25.w), + padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 24.w), child: GridView.builder( itemCount: bloc.productDatas.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 2, mainAxisSpacing: 14.h, - crossAxisSpacing: 4.5.w, + crossAxisSpacing: 6.w, ), itemBuilder: (BuildContext context, int index) { final productEntity = bloc.productDatas[index]; diff --git a/pubspec.yaml b/pubspec.yaml index 61ca7e4..4fcc959 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,11 +98,11 @@ dependencies: # 语音录制 https://pub.dev/packages/flutter_sound flutter_sound: ^9.2.13 # 音频播放 https://pub.dev/packages/audio_session - audio_session: ^0.1.16 + audio_session: ^0.1.19 # 文件管理 https://pub.dev/packages/path_provider path_provider: ^2.0.15 # 阿里云oss https://pub.dev/packages/flutter_oss_aliyun - flutter_oss_aliyun: ^6.2.7 + flutter_oss_aliyun: ^6.4.2 # App信息 https://pub.dev/packages/package_info_plus package_info_plus: ^4.2.0 # 应用内更新 https://pub-web.flutter-io.cn/packages/flutter_app_update @@ -135,6 +135,7 @@ flutter: assets: - assets/images/ - assets/fonts/ + - assets/sounds/ fonts: - family: HannotateSC fonts: