Commit 7c8ae103377406a71f677a4088c79ba18394ef7f

Authored by 吴启风
2 parents bdd7ee99 fc3c67bf

Merge branch 'work-wqf-20240715'

Showing 44 changed files with 538 additions and 220 deletions
.fvm/flutter_sdk
1   -/Users/stay/fvm/versions/3.19.2
2 1 \ No newline at end of file
  2 +/Users/biao/fvm/versions/3.19.2
3 3 \ No newline at end of file
... ...
.fvm/fvm_config.json
1 1 {
2   - "flutterSdkVersion": "3.19.2",
3   - "flavors": {}
  2 + "flutterSdkVersion": "3.19.2"
4 3 }
5 4 \ No newline at end of file
... ...
.vscode/settings.json 0 → 100644
  1 +{
  2 + "dart.flutterSdkPath": ".fvm/versions/3.19.2"
  3 +}
0 4 \ No newline at end of file
... ...
assets/images/micro_phone.gif

17.3 KB | W: | H:

17.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
assets/images/read_background.png 0 → 100644

480 KB

assets/images/reade_answer.gif

5.31 KB | W: | H:

4.78 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
assets/images/shop_desc.png 0 → 100644

746 KB

assets/images/voice.png

8.3 KB | W: | H:

8.91 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
assets/images/xe_shop.png 0 → 100644

267 KB

assets/sounds/class_time.mp3 0 → 100644
No preview for this file type
assets/sounds/count_with_me_instrumental.mp3 0 → 100644
No preview for this file type
assets/sounds/game_time.mp3 0 → 100644
No preview for this file type
assets/sounds/in_my_tummy_instrumental.mp3 0 → 100644
No preview for this file type
assets/sounds/music_time.mp3 0 → 100644
No preview for this file type
assets/sounds/quiz_time.mp3 0 → 100644
No preview for this file type
assets/sounds/reading_time.mp3 0 → 100644
No preview for this file type
assets/sounds/touch_instrumental.mp3 0 → 100644
No preview for this file type
assets/sounds/video_time.mp3 0 → 100644
No preview for this file type
assets/sounds/welcome_to_wow.mp3 0 → 100644
No preview for this file type
ios/Runner.xcodeproj/project.pbxproj
... ... @@ -2327,7 +2327,7 @@
2327 2327 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2328 2328 CODE_SIGN_IDENTITY = "Apple Development";
2329 2329 CODE_SIGN_STYLE = Automatic;
2330   - CURRENT_PROJECT_VERSION = 13;
  2330 + CURRENT_PROJECT_VERSION = 16;
2331 2331 DEVELOPMENT_TEAM = T8P9KW8GWH;
2332 2332 ENABLE_BITCODE = NO;
2333 2333 INFOPLIST_FILE = Runner/Info.plist;
... ... @@ -2671,7 +2671,7 @@
2671 2671 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2672 2672 CODE_SIGN_IDENTITY = "Apple Development";
2673 2673 CODE_SIGN_STYLE = Automatic;
2674   - CURRENT_PROJECT_VERSION = 13;
  2674 + CURRENT_PROJECT_VERSION = 16;
2675 2675 DEVELOPMENT_TEAM = T8P9KW8GWH;
2676 2676 ENABLE_BITCODE = NO;
2677 2677 HEADER_SEARCH_PATHS = (
... ... @@ -2876,7 +2876,7 @@
2876 2876 CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
2877 2877 CODE_SIGN_IDENTITY = "Apple Development";
2878 2878 CODE_SIGN_STYLE = Automatic;
2879   - CURRENT_PROJECT_VERSION = 13;
  2879 + CURRENT_PROJECT_VERSION = 16;
2880 2880 DEVELOPMENT_TEAM = T8P9KW8GWH;
2881 2881 ENABLE_BITCODE = NO;
2882 2882 INFOPLIST_FILE = Runner/Info.plist;
... ...
ios/Runner/Info.plist
... ... @@ -77,11 +77,11 @@
77 77 <true/>
78 78 </dict>
79 79 <key>NSCameraUsageDescription</key>
80   - <string>需要访问相机完成拍照</string>
  80 + <string>需要访问相机,完成修改头像功能</string>
81 81 <key>NSMicrophoneUsageDescription</key>
82   - <string>需要获取录音完成后续功能</string>
  82 + <string>需要获取录音,完成上课功能</string>
83 83 <key>NSPhotoLibraryUsageDescription</key>
84   - <string>需要获取照片用来修改头像</string>
  84 + <string>需要获取照片,完成上传头像功能</string>
85 85 <key>UIApplicationSupportsIndirectInputEvents</key>
86 86 <true/>
87 87 <key>UILaunchStoryboardName</key>
... ...
lib/common/core/app_consts.dart
... ... @@ -2,16 +2,26 @@ import &#39;../request/basic_config.dart&#39;;
2 2  
3 3 class AppConsts {
4 4 /// 隐私协议
5   - static const String userPrivacyPolicyUrl = 'http://page.kouyuxingqiu.com/wowenglishuserregister.html';
  5 + static const String userPrivacyPolicyUrl =
  6 + 'http://page.kouyuxingqiu.com/wowenglishuserregister.html';
6 7  
7 8 /// 儿童隐私协议
8   - static const String childrenPrivacyPolicyUrl = 'http://page.kouyuxingqiu.com/wowenglishchildprotect.html';
  9 + static const String childrenPrivacyPolicyUrl =
  10 + 'http://page.kouyuxingqiu.com/wowenglishchildprotect.html';
  11 +
  12 + /// 小鵝通
  13 + static const String xiaoeShopUrl = 'https://appo61s7g678876.h5.xiaoeknow.com';
9 14  
10 15 /// 与第三方共享协议
11   - static const String userTermSdkUrl = 'http://page.kouyuxingqiu.com/term_sdk.html';
  16 + static const String userTermSdkUrl =
  17 + 'http://page.kouyuxingqiu.com/term_sdk.html';
12 18  
13 19 /// 先声SDK
14   - static String xsAppKey = 'a418';
15   - static String xsAppSecretKey = BasicConfig.isTestDev?'1a16f31f2611bf32fb7b3fc38f5b2c81':'c11163aa6c834a028da4a4b30955be99';
16   - static String xsAppService = BasicConfig.isTestDev?'ws://trial.cloud.ssapi.cn:8080':'"wss://api.cloud.ssapi.cn';
  20 + static String xsAppKey = 'a418';
  21 + static String xsAppSecretKey = BasicConfig.isTestDev
  22 + ? '1a16f31f2611bf32fb7b3fc38f5b2c81'
  23 + : 'c11163aa6c834a028da4a4b30955be99';
  24 + static String xsAppService = BasicConfig.isTestDev
  25 + ? 'ws://trial.cloud.ssapi.cn:8080'
  26 + : '"wss://api.cloud.ssapi.cn';
17 27 }
... ...
lib/pages/games/bloc.dart
... ... @@ -2,17 +2,17 @@ import &#39;package:bloc/bloc.dart&#39;;
2 2 import 'package:flutter/cupertino.dart';
3 3 import 'package:flutter/services.dart';
4 4 import 'package:wow_english/common/extension/string_extension.dart';
  5 +import 'package:wow_english/utils/audio_player_util.dart';
5 6  
6 7 import 'event.dart';
7 8 import 'game_entity.dart';
8 9 import 'state.dart';
9 10  
10 11 class GamesBloc extends Bloc<GamesEvent, GamesState> {
11   -
12 12 late MethodChannel _methodChannel;
13 13  
14 14 //手动初始化4个GameEntity对象
15   - final List<GameEntity> _games = [
  15 + final List<GameEntity> _games = [
16 16 GameEntity()
17 17 ..id = 1
18 18 ..imageName = 'game_food_1'.assetPng
... ... @@ -31,7 +31,7 @@ class GamesBloc extends Bloc&lt;GamesEvent, GamesState&gt; {
31 31 ..name = 'Animal'
32 32 ];
33 33  
34   - List<GameEntity> get listData => _games;
  34 + List<GameEntity> get listData => _games;
35 35  
36 36 GamesBloc() : super(GamesState().init()) {
37 37 on<InitEvent>(_init);
... ... @@ -39,13 +39,16 @@ class GamesBloc extends Bloc&lt;GamesEvent, GamesState&gt; {
39 39 }
40 40  
41 41 void _init(InitEvent event, Emitter<GamesState> emit) async {
  42 + AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.inMyTummy);
42 43 emit(state.clone());
43 44 }
44 45  
45 46 void _gotoGamePage(GotoGamePageEvent event, Emitter<GamesState> emit) async {
  47 + await AudioPlayerUtil.getInstance().pause();
46 48 try {
47 49 _methodChannel = const MethodChannel('wow_english/game_method_channel');
48   - await _methodChannel.invokeMethod('openGamePage', { "gameId": event.gameId });
  50 + await _methodChannel
  51 + .invokeMethod('openGamePage', {"gameId": event.gameId});
49 52 } on PlatformException catch (e) {
50 53 debugPrint("Failed to go to native page: '${e.message}'.");
51 54 }
... ...
lib/pages/home/bloc.dart
  1 +import 'package:audioplayers/audioplayers.dart';
1 2 import 'package:bloc/bloc.dart';
  3 +import 'package:wow_english/common/core/user_util.dart';
  4 +import 'package:wow_english/common/extension/string_extension.dart';
  5 +import 'package:wow_english/utils/audio_player_util.dart';
2 6  
3 7 import '../../common/core/app_config_helper.dart';
4 8 import '../../common/request/dao/system_dao.dart';
... ... @@ -16,6 +20,9 @@ class HomeBloc extends Bloc&lt;HomeEvent, HomeState&gt; {
16 20 bool exchangeResult = false;
17 21  
18 22 void _init(InitEvent event, Emitter<HomeState> emit) async {
  23 + if (UserUtil.isLogined()) {
  24 + AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.welcomeToWow);
  25 + }
19 26 await _checkUpdate(emit);
20 27 }
21 28  
... ... @@ -37,7 +44,7 @@ class HomeBloc extends Bloc&lt;HomeEvent, HomeState&gt; {
37 44 return;
38 45 }
39 46 Log.d(
40   - "WQF _checkUpdate appVersionEntity: $appVersionEntity localVersion=$localVersion");
  47 + "HomeBloc _checkUpdate appVersionEntity: $appVersionEntity localVersion=$localVersion");
41 48 if (localVersion < int.parse(appVersionEntity.version ?? '0')) {
42 49 emit(UpdateDialogState(
43 50 appVersionEntity.volType == UpdateStrategy.FORCE.name,
... ...
lib/pages/home/view.dart
  1 +import 'package:audioplayers/audioplayers.dart';
1 2 import 'package:flutter/material.dart';
2 3 import 'package:flutter_app_update/azhon_app_update.dart';
3 4 import 'package:flutter_app_update/update_model.dart';
4 5 import 'package:flutter_bloc/flutter_bloc.dart';
5 6 import 'package:url_launcher/url_launcher.dart';
6 7 import 'package:wow_english/common/core/app_config_helper.dart';
  8 +import 'package:wow_english/common/core/app_consts.dart';
7 9 import 'package:wow_english/common/extension/string_extension.dart';
8 10 import 'package:wow_english/models/app_version_entity.dart';
9 11 import 'package:wow_english/pages/home/state.dart';
10 12 import 'package:wow_english/pages/home/widgets/BaseHomeHeaderWidget.dart';
11 13 import 'package:wow_english/pages/shop/exchane/bloc/exchange_lesson_bloc.dart';
12 14 import 'package:wow_english/pages/user/bloc/user_bloc.dart';
  15 +import 'package:wow_english/utils/audio_player_util.dart';
13 16  
14 17 import '../../common/core/user_util.dart';
15 18 import '../../common/dialogs/show_dialog.dart';
... ... @@ -57,7 +60,9 @@ class _HomePageView extends StatelessWidget {
57 60 child: Column(
58 61 children: [
59 62 BaseHomeHeaderWidget(
60   - callBack: (value) => {
  63 + callBack: (value) async => {
  64 + await AudioPlayerUtil.getInstance()
  65 + .playAudio(AudioPlayerUtilType.touch),
61 66 bloc.exchangeResult = value['exchange'],
62 67 bloc.add(ExchangeSuccessEvent())
63 68 }),
... ... @@ -68,7 +73,9 @@ class _HomePageView extends StatelessWidget {
68 73 Expanded(
69 74 child: GestureDetector(
70 75 onTap: () {
71   - _checkPermission(() {
  76 + _checkPermission(() async {
  77 + await AudioPlayerUtil.getInstance()
  78 + .playAudio(AudioPlayerUtilType.classTime);
72 79 pushNamed(AppRouteName.courseUnit)
73 80 .then((value) => {
74 81 if (value != null)
... ... @@ -102,13 +109,45 @@ class _HomePageView extends StatelessWidget {
102 109 ),
103 110 ),
104 111 ),
  112 + BlocBuilder<UserBloc, UserState>(
  113 + builder: (context, userState) {
  114 + return GestureDetector(
  115 + onTap: () {
  116 + _checkPermission(() async {
  117 + await AudioPlayerUtil.getInstance().pause();
  118 + Navigator.of(context).pushNamed(
  119 + AppRouteName.webView,
  120 + arguments: {
  121 + 'urlStr': AppConsts.xiaoeShopUrl,
  122 + 'webViewTitle': 'Wow精选'
  123 + }).then((value) async => {
  124 + await AudioPlayerUtil.getInstance().playAudio(
  125 + AudioPlayerUtilType.touch),
  126 + });
  127 + }, bloc);
  128 + },
  129 + child: Offstage(
  130 + offstage: AppConfigHelper.shouldHidePay() ||
  131 + !UserUtil.isLogined(),
  132 + child: Image.asset('xe_shop'.assetPng,
  133 + width: 140.5.w, height: 172.h),
  134 + ));
  135 + }),
105 136 Expanded(
106 137 child: BlocBuilder<UserBloc, UserState>(
107 138 builder: (context, userState) {
108 139 return GestureDetector(
109 140 onTap: () {
110   - _checkPermission(() {
111   - pushNamed(AppRouteName.games);
  141 + _checkPermission(() async {
  142 + await AudioPlayerUtil.getInstance()
  143 + .playAudio(
  144 + AudioPlayerUtilType.gameTime);
  145 + pushNamed(AppRouteName.games)
  146 + .then((value) => {
  147 + AudioPlayerUtil.getInstance()
  148 + .playAudio(AudioPlayerUtilType
  149 + .touch),
  150 + });
112 151 }, bloc);
113 152 },
114 153 child: Column(
... ... @@ -157,6 +196,8 @@ class _HomePageView extends StatelessWidget {
157 196 }, rightTap: () {
158 197 popPage();
159 198 pushNamed(AppRouteName.shop).then((value) {
  199 + AudioPlayerUtil.getInstance()
  200 + .playAudio(AudioPlayerUtilType.touch);
160 201 if (value != null) {
161 202 bloc.exchangeResult = value['exchange'];
162 203 bloc.add(ExchangeSuccessEvent());
... ...
lib/pages/home/widgets/BaseHomeHeaderWidget.dart
... ... @@ -3,6 +3,7 @@ import &#39;package:flutter_bloc/flutter_bloc.dart&#39;;
3 3 import 'package:flutter_screenutil/flutter_screenutil.dart';
4 4 import 'package:wow_english/common/core/app_config_helper.dart';
5 5 import 'package:wow_english/common/extension/string_extension.dart';
  6 +import 'package:wow_english/utils/audio_player_util.dart';
6 7  
7 8 import '../../../common/core/user_util.dart';
8 9 import '../../../models/course_entity.dart';
... ... @@ -83,7 +84,8 @@ class BaseHomeHeaderWidget extends StatelessWidget {
83 84 offstage: AppConfigHelper.shouldHidePay() ||
84 85 !UserUtil.isLogined(),
85 86 child: GestureDetector(
86   - onTap: () => {
  87 + onTap: () async => {
  88 + await AudioPlayerUtil.getInstance().pause(),
87 89 pushNamed(AppRouteName.shop).then((value) {
88 90 if (value != null) {
89 91 if (callBack == null) {
... ... @@ -113,8 +115,9 @@ class BaseHomeHeaderWidget extends StatelessWidget {
113 115 );
114 116 }
115 117  
116   - void onUserClick() {
  118 + Future<void> onUserClick() async {
117 119 if (UserUtil.isLogined()) {
  120 + await AudioPlayerUtil.getInstance().pause();
118 121 pushNamed(AppRouteName.user).then((value) {
119 122 if (value != null) {
120 123 if (callBack == null) {
... ...
lib/pages/practice/topic_picture_page.dart
... ... @@ -59,7 +59,12 @@ class _TopicPicturePage extends StatelessWidget {
59 59 builder: (context, state) {
60 60 final bloc = BlocProvider.of<TopicPictureBloc>(context);
61 61 return Container(
62   - color: Colors.white,
  62 + decoration: BoxDecoration(
  63 + image: DecorationImage(
  64 + image: AssetImage('read_background'.assetPng), // 背景图片路径
  65 + fit: BoxFit.cover, // 适应图片的方式
  66 + ),
  67 + ),
63 68 child: Stack(
64 69 children: [
65 70 Column(
... ... @@ -75,6 +80,7 @@ class _TopicPicturePage extends StatelessWidget {
75 80 // Navigator.pop(context);
76 81 },
77 82 ),
  83 + 35.verticalSpace,
78 84 Expanded(
79 85 child: PageView.builder(
80 86 itemCount: bloc.entity?.topics?.length,
... ... @@ -109,12 +115,7 @@ class _TopicPicturePage extends StatelessWidget {
109 115 }),
110 116 )
111 117 ],
112   - ),
113   - Positioned(
114   - left: 0,
115   - right: 0,
116   - bottom: 0,
117   - child: Image.asset('bottom_grass'.assetPng))
  118 + )
118 119 ],
119 120 ),
120 121 );
... ... @@ -299,7 +300,7 @@ class _TopicPicturePage extends StatelessWidget {
299 300 26.verticalSpace,
300 301 SizedBox(
301 302 height: 143.h,
302   - width: 163.w * (topics?.topicAnswerList?.length ?? 0),
  303 + width: 203.w * (topics?.topicAnswerList?.length ?? 0),
303 304 child: ListView.builder(
304 305 scrollDirection: Axis.horizontal,
305 306 physics: const NeverScrollableScrollPhysics(),
... ... @@ -321,7 +322,7 @@ class _TopicPicturePage extends StatelessWidget {
321 322 builder: (context, state) {
322 323 final bloc = BlocProvider.of<TopicPictureBloc>(context);
323 324 return Container(
324   - padding: EdgeInsets.symmetric(horizontal: 10.w),
  325 + padding: EdgeInsets.symmetric(horizontal: 20.w),
325 326 child: GestureDetector(
326 327 onTap: () => bloc.add(SelectItemEvent(index)),
327 328 child: Container(
... ... @@ -333,15 +334,13 @@ class _TopicPicturePage extends StatelessWidget {
333 334 borderRadius: BorderRadius.circular(15),
334 335 ),
335 336 height: 143.h,
336   - width: 143.w,
  337 + width: 163.w,
337 338 child: Container(
338 339 decoration: BoxDecoration(
339 340 color: Colors.white,
340 341 borderRadius: BorderRadius.circular(15),
341   - border: Border.all(
342   - width: 1.0, color: const Color(0xFF140C10)),
343 342 image: DecorationImage(
344   - fit: BoxFit.fitWidth,
  343 + fit: BoxFit.fill,
345 344 image: NetworkImage(answerList?.picUrl ?? ''))),
346 345 ),
347 346 ),
... ... @@ -449,12 +448,18 @@ class _TopicPicturePage extends StatelessWidget {
449 448 return Row(
450 449 mainAxisAlignment: MainAxisAlignment.center,
451 450 children: [
452   - OwImageWidget(
453   - name: topics?.picUrl ?? '',
454   - height: 186.h,
455   - width: 186.w,
  451 + ClipRRect(
  452 + borderRadius: BorderRadius.circular(20),
  453 + child: Container(
  454 + color: Colors.white,
  455 + child: OwImageWidget(
  456 + name: topics?.picUrl ?? '',
  457 + height: 186.h,
  458 + width: 186.w,
  459 + ),
  460 + ),
456 461 ),
457   - 160.horizontalSpace,
  462 + 120.horizontalSpace,
458 463 Column(
459 464 mainAxisAlignment: MainAxisAlignment.center,
460 465 children: [
... ... @@ -472,8 +477,8 @@ class _TopicPicturePage extends StatelessWidget {
472 477 bloc.voicePlayState == VoicePlayState.playing
473 478 ? 'reade_answer'.assetGif
474 479 : 'voice'.assetPng,
475   - height: 52.h,
476   - width: 46.w,
  480 + height: 45.h,
  481 + width: 45.w,
477 482 ),
478 483 10.horizontalSpace,
479 484 Text(topics?.word ?? '')
... ... @@ -506,8 +511,8 @@ class _TopicPicturePage extends StatelessWidget {
506 511 bloc.isVoicing
507 512 ? 'micro_phone'.assetGif
508 513 : 'micro_phone'.assetPng,
509   - height: 75.w,
510   - width: 75.w,
  514 + height: 46.h,
  515 + width: 46.w,
511 516 ),
512 517 )
513 518 ],
... ...
lib/pages/practice/widgets/practice_header_widget.dart
... ... @@ -12,9 +12,10 @@ class PracticeHeaderWidget extends StatelessWidget {
12 12 @override
13 13 Widget build(BuildContext context) {
14 14 return Container(
15   - color: Colors.white,
  15 + color: Colors.transparent,
16 16 height: kToolbarHeight + 3.h,
17 17 child: AppBar(
  18 + backgroundColor: Colors.transparent,
18 19 leading: GestureDetector(
19 20 child: Image.asset(
20 21 'back_around'.assetPng,
... ... @@ -25,7 +26,7 @@ class PracticeHeaderWidget extends StatelessWidget {
25 26 ),
26 27 centerTitle: true,
27 28 title: Container(
28   - height: 40.h,
  29 + height: 20.h,
29 30 width: 100.w, // 容器宽度
30 31 // padding: EdgeInsets.symmetric(horizontal: 27.w, vertical: 10.h),
31 32 alignment: Alignment.center,
... ...
lib/pages/reading/bloc/reading_event.dart
... ... @@ -39,7 +39,7 @@ class XSVoiceStartEvent extends ReadingPageEvent {
39 39 final String content;
40 40 final String type;
41 41 final String? userId;
42   - XSVoiceStartEvent(this.content,this.type,this.userId);
  42 + XSVoiceStartEvent(this.content, this.type, this.userId);
43 43 }
44 44  
45 45 ///先声评测停止
... ... @@ -52,4 +52,7 @@ class OnXSVoiceStateChangeEvent extends ReadingPageEvent {}
52 52 class VoicePlayStateChangeEvent extends ReadingPageEvent {}
53 53  
54 54 ///录音播放
55   -class PlayRecordAudioEvent extends ReadingPageEvent {}
56 55 \ No newline at end of file
  56 +class PlayRecordAudioEvent extends ReadingPageEvent {}
  57 +
  58 +///播放下一页
  59 +class PlayNextPageEvent extends ReadingPageEvent {}
... ...
lib/pages/reading/reading_page.dart
... ... @@ -21,17 +21,16 @@ class ReadingPage extends StatelessWidget {
21 21 @override
22 22 Widget build(BuildContext context) {
23 23 return BlocProvider(
24   - create: (_) => ReadingPageBloc(context, PageController(), courseLessonId ?? '')
25   - ..add(InitBlocEvent())
26   - ..add(RequestDataEvent())
27   - ..add(XSVoiceInitEvent(
28   - {
29   - 'appKey':AppConsts.xsAppKey,
30   - 'service':AppConsts.xsAppService,
31   - 'secretKey':AppConsts.xsAppSecretKey,
32   - 'userId':UserUtil.getUser()!.id.toString(),
33   - }
34   - )),
  24 + create: (_) =>
  25 + ReadingPageBloc(context, PageController(), courseLessonId ?? '')
  26 + ..add(InitBlocEvent())
  27 + ..add(RequestDataEvent())
  28 + ..add(XSVoiceInitEvent({
  29 + 'appKey': AppConsts.xsAppKey,
  30 + 'service': AppConsts.xsAppService,
  31 + 'secretKey': AppConsts.xsAppSecretKey,
  32 + 'userId': UserUtil.getUser()!.id.toString(),
  33 + })),
35 34 child: _ReadingPage(),
36 35 );
37 36 }
... ... @@ -60,8 +59,8 @@ class _ReadingPage extends StatelessWidget {
60 59 );
61 60 }
62 61  
63   - Widget _readingPageView() => BlocBuilder<ReadingPageBloc, ReadingPageState>(
64   - builder: (context, state) {
  62 + Widget _readingPageView() =>
  63 + BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) {
65 64 final bloc = BlocProvider.of<ReadingPageBloc>(context);
66 65 return Container(
67 66 color: Colors.white,
... ... @@ -84,16 +83,14 @@ class _ReadingPage extends StatelessWidget {
84 83 children: [
85 84 Padding(
86 85 padding:
87   - EdgeInsets.only(left: ScreenUtil().bottomBarHeight),
  86 + EdgeInsets.only(left: ScreenUtil().bottomBarHeight),
88 87 child: IconButton(
89 88 onPressed: () {
90   - popPage(
91   - data:{
92   - 'currentStep':bloc.currentPage,
93   - 'courseLessonId':bloc.courseLessonId,
94   - 'isCompleted':bloc.isLastPage(),
95   - }
96   - );
  89 + popPage(data: {
  90 + 'currentStep': bloc.currentPage,
  91 + 'courseLessonId': bloc.courseLessonId,
  92 + 'isCompleted': bloc.isLastPage(),
  93 + });
97 94 },
98 95 icon: Image.asset(
99 96 'back_around'.assetPng,
... ... @@ -158,6 +155,7 @@ class _ReadingPage extends StatelessWidget {
158 155 margin: EdgeInsets.symmetric(horizontal: 10.w),
159 156 child: Row(
160 157 children: [
  158 + 5.horizontalSpace,
161 159 GestureDetector(
162 160 onTap: () {
163 161 if (bloc.isRecording) {
... ... @@ -167,7 +165,7 @@ class _ReadingPage extends StatelessWidget {
167 165 },
168 166 child: Image.asset(
169 167 bloc.voicePlayState == VoicePlayState.playing &&
170   - bloc.isOriginAudioPlaying
  168 + bloc.isOriginAudioPlaying
171 169 ? 'reade_answer'.assetGif
172 170 : 'voice'.assetPng,
173 171 height: 40.h,
... ... @@ -179,12 +177,12 @@ class _ReadingPage extends StatelessWidget {
179 177 ),
180 178 Expanded(
181 179 child: Text(
182   - bloc.currentPageData()?.word?.trim() ?? '',
183   - style: TextStyle(
184   - color: const Color(0xFF333333), fontSize: 21.sp),
185   - maxLines: 2,
186   - overflow: TextOverflow.ellipsis,
187   - )),
  180 + bloc.currentPageData()?.word?.trim() ?? '',
  181 + style: TextStyle(
  182 + color: const Color(0xFF333333), fontSize: 21.sp),
  183 + maxLines: 2,
  184 + overflow: TextOverflow.ellipsis,
  185 + )),
188 186 SizedBox(
189 187 width: 10.w,
190 188 ),
... ... @@ -241,8 +239,7 @@ class _ReadingPage extends StatelessWidget {
241 239 return Stack(
242 240 children: [
243 241 Positioned.fill(
244   - child:
245   - Image.network(readings.picUrl ?? '', fit: BoxFit.cover),
  242 + child: Image.network(readings.picUrl ?? '', fit: BoxFit.cover),
246 243 ),
247 244 ],
248 245 );
... ...
lib/pages/section/bloc/section_bloc.dart
  1 +import 'package:audioplayers/audioplayers.dart';
1 2 import 'package:flutter/cupertino.dart';
2 3 import 'package:flutter/foundation.dart';
3 4 import 'package:flutter/material.dart';
4 5 import 'package:flutter_bloc/flutter_bloc.dart';
5 6 import 'package:flutter_screenutil/flutter_screenutil.dart';
  7 +import 'package:wow_english/common/extension/string_extension.dart';
6 8 import 'package:wow_english/common/request/dao/lesson_dao.dart';
7 9 import 'package:wow_english/common/request/exception.dart';
8 10 import 'package:wow_english/common/request/dao/listen_dao.dart';
9 11 import 'package:wow_english/models/course_process_entity.dart';
  12 +import 'package:wow_english/utils/audio_player_util.dart';
10 13 import 'package:wow_english/utils/loading.dart';
11 14 import 'package:wow_english/utils/toast_util.dart';
12 15  
... ... @@ -61,16 +64,20 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
61 64 on<RequestEnterClassEvent>(_requestEnterClass);
62 65 on<RequestVideoLessonEvent>(_requestVideoLesson);
63 66 on<CurrentUnitIndexChangeEvent>(_pageControllerChange);
  67 + on<InitEvent>((event, emit) async {
  68 + await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe);
  69 + });
64 70 }
65 71  
66 72 void _requestSectionsData(
67 73 RequestDataEvent event, Emitter<SectionState> emitter) async {
68 74 try {
69 75 await loading(() async {
70   - List<CourseSectionEntity>? courseSectionEntities = await LessonDao.courseSection(courseUnitId: event.courseUnitId);
  76 + List<CourseSectionEntity>? courseSectionEntities =
  77 + await LessonDao.courseSection(courseUnitId: event.courseUnitId);
71 78 if (courseSectionEntities != null) {
72 79 _courseSectionDatasMap[event.courseUnitId] =
73   - await LessonDao.courseSection(courseUnitId: event.courseUnitId);
  80 + await LessonDao.courseSection(courseUnitId: event.courseUnitId);
74 81 emitter(LessonDataLoadState());
75 82 }
76 83 });
... ... @@ -224,7 +231,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
224 231 ///查找当前unit的下一个section
225 232 CourseSectionEntity? nextCourseSectionEntity =
226 233 findCourseSectionBySort(curSectionSort + 1);
227   - return checkCourseSectionLocked(courseLessonId, nextCourseSectionEntity, emitter);
  234 + return checkCourseSectionLocked(
  235 + courseLessonId, nextCourseSectionEntity, emitter);
228 236 } catch (e) {
229 237 if (e is ApiException) {
230 238 showToast(e.message.toString());
... ... @@ -234,7 +242,9 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
234 242 }
235 243  
236 244 ///检查section是否锁定
237   - Future<CourseSectionEntity?> checkCourseSectionLocked(int courseLessonId, CourseSectionEntity? courseSectionEntity,
  245 + Future<CourseSectionEntity?> checkCourseSectionLocked(
  246 + int courseLessonId,
  247 + CourseSectionEntity? courseSectionEntity,
238 248 Emitter<SectionState> emitter) async {
239 249 if (courseSectionEntity != null) {
240 250 if (courseSectionEntity.lock == false) {
... ... @@ -243,15 +253,15 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
243 253 } else {
244 254 ///如果section锁了,请求当前unit下的section数据,查询解锁状态
245 255 int courseUnitId = courseSectionEntity.courseUnitId;
246   - CourseSectionEntity? result = await loading(() async {
  256 + CourseSectionEntity? result = await loading(() async {
247 257 List<CourseSectionEntity>? tempSectionEntities =
248   - await LessonDao.courseSection(courseUnitId: courseUnitId);
  258 + await LessonDao.courseSection(courseUnitId: courseUnitId);
249 259 if (tempSectionEntities != null) {
250 260 _courseSectionDatasMap[courseUnitId] = tempSectionEntities;
251 261 emitter(LessonDataLoadState());
252 262 }
253 263 courseSectionEntity = tempSectionEntities?.firstWhereOrNull(
254   - (element) => element.id == courseSectionEntity?.id);
  264 + (element) => element.id == courseSectionEntity?.id);
255 265 if (courseSectionEntity?.lock == false) {
256 266 ///刷新后的数据如果解锁了,直接返回
257 267 return courseSectionEntity;
... ... @@ -270,18 +280,20 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
270 280 if (curCourseUnitDetail != null) {
271 281 ///再根据当前unit的sortOrder找出下一个unit
272 282 CourseUnitDetail? nextCourseUnitDetail =
273   - _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
274   - element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
  283 + _courseUnitEntity.courseUnitVOList?.firstWhereOrNull((element) =>
  284 + element.sortOrder == (curCourseUnitDetail.sortOrder! + 1));
275 285  
276 286 if (nextCourseUnitDetail != null) {
277 287 if (nextCourseUnitDetail.lock == true) {
278 288 ///如果下一个unit是锁定状态,请求数据刷新查询解锁状态
279 289 CourseSectionEntity? result = await loading(() async {
280   - CourseUnitEntity? newCourseUnitEntity = await LessonDao.courseUnit(
281   - _courseUnitEntity.nowCourseModuleId);
  290 + CourseUnitEntity? newCourseUnitEntity =
  291 + await LessonDao.courseUnit(
  292 + _courseUnitEntity.nowCourseModuleId);
282 293  
283 294 ///拿到重新获取到的unit后,再次判断是否解锁
284   - nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList?.firstWhereOrNull(
  295 + nextCourseUnitDetail = newCourseUnitEntity?.courseUnitVOList
  296 + ?.firstWhereOrNull(
285 297 (element) => element.id == nextCourseUnitDetail?.id);
286 298 if (nextCourseUnitDetail?.lock == false) {
287 299 ///解锁状态从锁定到解锁,覆盖原unit数据并刷新ui
... ... @@ -289,7 +301,8 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
289 301 courseUnitEntityChanged = true;
290 302 emitter(LessonDataLoadState());
291 303  
292   - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail!.id!, emitter);
  304 + return checkCourseSectionLockedOfNextUnit(
  305 + courseLessonId, nextCourseUnitDetail!.id!, emitter);
293 306 } else {
294 307 showToast('下个单元课程还没解锁哦');
295 308  
... ... @@ -299,10 +312,12 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
299 312 });
300 313 return result;
301 314 } else {
302   - return checkCourseSectionLockedOfNextUnit(courseLessonId, nextCourseUnitDetail.id!, emitter);
  315 + return checkCourseSectionLockedOfNextUnit(
  316 + courseLessonId, nextCourseUnitDetail.id!, emitter);
303 317 }
304 318 } else {
305 319 showToast("恭喜你,本阶段学到顶啦");
  320 +
306 321 ///最后一个unit了
307 322 return null;
308 323 }
... ... @@ -314,13 +329,16 @@ class SectionBloc extends Bloc&lt;SectionEvent, SectionState&gt; {
314 329 }
315 330  
316 331 ///检查下一个unit的(第一个)section
317   - Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(int courseLessonId, int nextCourseUnitDetailId,
  332 + Future<CourseSectionEntity?> checkCourseSectionLockedOfNextUnit(
  333 + int courseLessonId,
  334 + int nextCourseUnitDetailId,
318 335 Emitter<SectionState> emitter) async {
319   - CourseSectionEntity? firstSectionNextUnit = await getFirstSectionByUnitId(
320   - nextCourseUnitDetailId, emitter);
  336 + CourseSectionEntity? firstSectionNextUnit =
  337 + await getFirstSectionByUnitId(nextCourseUnitDetailId, emitter);
321 338 if (firstSectionNextUnit != null) {
322 339 ///下个unit的第一个section如果不为空,再次检查是否锁定
323   - CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(courseLessonId, firstSectionNextUnit, emitter);
  340 + CourseSectionEntity? courseSectionEntity = await checkCourseSectionLocked(
  341 + courseLessonId, firstSectionNextUnit, emitter);
324 342 if (courseSectionEntity != null) {
325 343 ///只有是下一unit的第一个section并且解锁了,才跳转
326 344 _pageController.nextPage(
... ...
lib/pages/section/bloc/section_event.dart
... ... @@ -9,6 +9,8 @@ class RequestDataEvent extends SectionEvent {
9 9 RequestDataEvent(this.courseUnitId);
10 10 }
11 11  
  12 +class InitEvent extends SectionEvent {}
  13 +
12 14 ///获取视频课程内容
13 15 class RequestVideoLessonEvent extends SectionEvent {
14 16 final String courseLessonId;
... ...
lib/pages/section/section_page.dart
  1 +import 'package:audioplayers/audioplayers.dart';
1 2 import 'package:flutter/cupertino.dart';
2 3 import 'package:flutter/material.dart';
3 4 import 'package:flutter_bloc/flutter_bloc.dart';
... ... @@ -11,6 +12,8 @@ import &#39;package:wow_english/pages/section/widgets/section_item.dart&#39;;
11 12 import 'package:wow_english/pages/section/widgets/section_bouns_item.dart';
12 13 import 'package:wow_english/pages/section/widgets/section_header_widget.dart';
13 14 import 'package:wow_english/route/route.dart';
  15 +import 'package:wow_english/utils/audio_player_util.dart';
  16 +import 'package:wow_english/utils/log_util.dart';
14 17 import 'package:wow_english/utils/toast_util.dart';
15 18  
16 19 import '../../models/course_section_entity.dart';
... ... @@ -38,7 +41,8 @@ class SectionPage extends StatelessWidget {
38 41 initialPage,
39 42 PageController(initialPage: initialPage),
40 43 ScrollController(),
41   - ScrollController()),
  44 + ScrollController())
  45 + ..add(InitEvent()),
42 46 //为了触发指示器进入后计算位置
43 47 // ..add(CurrentUnitIndexChangeEvent(initialPage)),
44 48 child: _SectionPageView(context),
... ... @@ -55,7 +59,7 @@ class _SectionPageView extends StatelessWidget {
55 59 Widget build(BuildContext context) {
56 60 final bloc = BlocProvider.of<SectionBloc>(context);
57 61 return BlocListener<SectionBloc, SectionState>(
58   - listener: (context, state) {
  62 + listener: (context, state) async {
59 63 if (state is RequestVideoLessonState) {
60 64 final videoUrl = bloc.processEntity?.videos?.videoUrl ?? '';
61 65 var title = '';
... ... @@ -87,6 +91,8 @@ class _SectionPageView extends StatelessWidget {
87 91 currentTime: dataMap['currentTime'],
88 92 autoNextSection: dataMap['nextSection']));
89 93 }
  94 + AudioPlayerUtil.getInstance()
  95 + .playAudio(AudioPlayerUtilType.countWithMe);
90 96 });
91 97 return;
92 98 }
... ... @@ -96,12 +102,22 @@ class _SectionPageView extends StatelessWidget {
96 102 state.courseType != SectionType.pictureBook.value) {
97 103 ///视频类型
98 104 ///获取视频课程内容
  105 + if (state.courseType == 1) {
  106 + await AudioPlayerUtil.getInstance()
  107 + .playAudio(AudioPlayerUtilType.musicTime);
  108 + } else {
  109 + await AudioPlayerUtil.getInstance()
  110 + .playAudio(AudioPlayerUtilType.videoTime);
  111 + }
99 112 bloc.add(RequestVideoLessonEvent(
100 113 state.courseLessonId, state.courseType));
  114 +
101 115 return;
102 116 }
103 117  
104 118 if (state.courseType == SectionType.pictureBook.value) {
  119 + await AudioPlayerUtil.getInstance()
  120 + .playAudio(AudioPlayerUtilType.readingTime);
105 121 //绘本
106 122 pushNamed(AppRouteName.reading,
107 123 arguments: {'courseLessonId': state.courseLessonId})
... ... @@ -114,13 +130,18 @@ class _SectionPageView extends StatelessWidget {
114 130 currentStep: dataMap['currentStep'],
115 131 autoNextSection: dataMap['nextSection'],
116 132 ));
  133 + AudioPlayerUtil.getInstance()
  134 + .playAudio(AudioPlayerUtilType.countWithMe);
117 135 }
118 136 });
  137 +
119 138 return;
120 139 }
121 140  
122 141 if (state.courseType == SectionType.practice.value) {
123 142 //练习
  143 + await AudioPlayerUtil.getInstance()
  144 + .playAudio(AudioPlayerUtilType.quizTime);
124 145 pushNamed(AppRouteName.topicPic,
125 146 arguments: {'courseLessonId': state.courseLessonId})
126 147 .then((value) {
... ... @@ -131,6 +152,8 @@ class _SectionPageView extends StatelessWidget {
131 152 currentStep: dataMap['currentStep'],
132 153 autoNextSection: dataMap['nextSection']));
133 154 }
  155 + AudioPlayerUtil.getInstance()
  156 + .playAudio(AudioPlayerUtilType.countWithMe);
134 157 });
135 158 return;
136 159 }
... ...
lib/pages/shop/home/shop_desc_page.dart 0 → 100644
  1 +import 'package:flutter/material.dart';
  2 +
  3 +import 'package:wow_english/common/extension/string_extension.dart';
  4 +import 'package:wow_english/common/widgets/we_app_bar.dart';
  5 +
  6 +///购前须知页
  7 +class ShopDescPage extends StatelessWidget {
  8 + const ShopDescPage({super.key});
  9 +
  10 + @override
  11 + Widget build(BuildContext context) {
  12 + return _ShopDescPageView();
  13 + }
  14 +}
  15 +
  16 +class _ShopDescPageView extends StatelessWidget {
  17 +
  18 + @override
  19 + Widget build(BuildContext context) {
  20 + return Scaffold(
  21 + appBar: const WEAppBar(
  22 + titleText: '购前须知',
  23 + centerTitle: true,
  24 + ),
  25 + body: SingleChildScrollView(
  26 + child: Center(
  27 + child: Image.asset('shop_desc'.assetPng),
  28 + ),
  29 + ),
  30 + );
  31 + }
  32 +}
... ...
lib/pages/shop/home/shop_home_page.dart
... ... @@ -58,7 +58,7 @@ class _ShopHomeView extends StatelessWidget {
58 58 ),
59 59 color: Colors.white,
60 60 onPressed: () {
61   - showToast('购前须知');
  61 + pushNamed(AppRouteName.shopDesc);
62 62 },
63 63 )
64 64 ],
... ...
lib/pages/unit/bloc.dart
1 1 import 'package:bloc/bloc.dart';
2 2 import 'package:wow_english/pages/unit/widget/home_tab_header_widget.dart';
  3 +import 'package:wow_english/utils/audio_player_util.dart';
3 4  
4 5 import '../../common/request/dao/lesson_dao.dart';
5 6 import '../../common/request/exception.dart';
... ... @@ -24,6 +25,9 @@ class UnitBloc extends Bloc&lt;UnitEvent, UnitState&gt; {
24 25  
25 26 UnitBloc(CourseModuleEntity? courseEntity) : super(UnitState().init()) {
26 27 on<RequestUnitDataEvent>(_requestUnitDatas);
  28 + on<UnitInitEvent>((event, emit) {
  29 + AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.inMyTummy);
  30 + });
27 31 }
28 32  
29 33 void _requestUnitDatas(
... ... @@ -44,16 +48,28 @@ class UnitBloc extends Bloc&lt;UnitEvent, UnitState&gt; {
44 48 return _moduleEntity?.code ?? _unitData?.courseModuleCode;
45 49 }
46 50  
47   - void headerActionEvent(HeaderActionType type) {
  51 + Future<void> headerActionEvent(HeaderActionType type) async {
  52 + await AudioPlayerUtil.getInstance().pause();
48 53 if (type == HeaderActionType.video) {
  54 + //视频跟读暂时隐藏了
49 55 pushNamed(AppRouteName.reAfter);
50 56 } else if (type == HeaderActionType.phase) {
51   - pushNamed(AppRouteName.courseModule);
  57 + pushNamed(AppRouteName.courseModule).then((value) => {
  58 + AudioPlayerUtil.getInstance()
  59 + .playAudio(AudioPlayerUtilType.inMyTummy)
  60 + });
  61 + ;
52 62 } else if (type == HeaderActionType.listen) {
53   - pushNamed(AppRouteName.listen);
  63 + pushNamed(AppRouteName.listen).then((value) => {
  64 + AudioPlayerUtil.getInstance()
  65 + .playAudio(AudioPlayerUtilType.inMyTummy)
  66 + });
54 67 } else if (type == HeaderActionType.shop) {
55   - pushNamed(AppRouteName.shop)
56   - .then((value) => {exchangeResult = value['exchange']});
  68 + pushNamed(AppRouteName.shop).then((value) => {
  69 + AudioPlayerUtil.getInstance()
  70 + .playAudio(AudioPlayerUtilType.inMyTummy),
  71 + exchangeResult = value['exchange']
  72 + });
57 73 } else if (type == HeaderActionType.user) {
58 74 pushNamed(AppRouteName.user);
59 75 }
... ...
lib/pages/unit/event.dart
... ... @@ -6,3 +6,5 @@ class RequestUnitDataEvent extends UnitEvent {
6 6  
7 7 RequestUnitDataEvent(this.moduleId);
8 8 }
  9 +
  10 +class UnitInitEvent extends UnitEvent {}
... ...
lib/pages/unit/view.dart
... ... @@ -5,6 +5,7 @@ import &#39;package:wow_english/pages/unit/state.dart&#39;;
5 5 import 'package:wow_english/pages/unit/widget/course_unit_item.dart';
6 6 import 'package:wow_english/pages/unit/widget/home_tab_header_widget.dart';
7 7 import 'package:wow_english/route/route.dart';
  8 +import 'package:wow_english/utils/audio_player_util.dart';
8 9  
9 10 import '../../models/course_module_entity.dart';
10 11 import '../../models/course_unit_entity.dart';
... ... @@ -23,6 +24,7 @@ class UnitPage extends StatelessWidget {
23 24 Widget build(BuildContext context) {
24 25 return BlocProvider(
25 26 create: (BuildContext context) => UnitBloc(courseModuleEntity)
  27 + ..add(UnitInitEvent())
26 28 ..add(RequestUnitDataEvent(courseModuleEntity?.id)),
27 29 child: Builder(builder: (context) => _buildPage(context)),
28 30 );
... ... @@ -41,6 +43,8 @@ class UnitPage extends StatelessWidget {
41 43 HomeTabHeaderWidget(
42 44 courseModuleCode: bloc.getCourseModuleCode(),
43 45 onBack: () {
  46 + AudioPlayerUtil.getInstance()
  47 + .playAudio(AudioPlayerUtilType.touch);
44 48 popPage(data: {'exchange': bloc.exchangeResult});
45 49 },
46 50 actionTap: (HeaderActionType type) {
... ... @@ -58,17 +62,19 @@ class UnitPage extends StatelessWidget {
58 62 CourseUnitDetail? data =
59 63 bloc.unitData?.courseUnitVOList?[index];
60 64 return GestureDetector(
61   - onTap: () {
  65 + onTap: () async {
62 66 if (data.lock == true) {
63 67 showToast('当前单元课程暂未解锁');
64 68 return;
65 69 }
66   -
  70 + // await AudioPlayerUtil.getInstance().pause();
67 71 pushNamed(AppRouteName.courseSection,
68 72 arguments: {
69 73 'courseUnitEntity': bloc.unitData,
70 74 'courseUnitId': data.id
71 75 }).then((value) {
  76 + AudioPlayerUtil.getInstance()
  77 + .playAudio(AudioPlayerUtilType.inMyTummy);
72 78 if (value != null) {
73 79 Map<String, dynamic> dataMap =
74 80 value as Map<String, dynamic>;
... ...
lib/pages/unit/widget/course_unit_item.dart
... ... @@ -17,14 +17,13 @@ class CourseUnitItem extends StatelessWidget {
17 17 @override
18 18 Widget build(BuildContext context) {
19 19 return Padding(
20   - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h),
21   - child: Stack(
22   - children: [
23   - _normalItem(),
24   - _lockWidget(),
25   - ],
26   - )
27   - );
  20 + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 24.h),
  21 + child: Stack(
  22 + children: [
  23 + _normalItem(),
  24 + _lockWidget(),
  25 + ],
  26 + ));
28 27 }
29 28  
30 29 Widget _normalItem() {
... ... @@ -40,17 +39,17 @@ class CourseUnitItem extends StatelessWidget {
40 39 children: [
41 40 Expanded(
42 41 child: Container(
43   - decoration: BoxDecoration(
44   - border: Border.all(
45   - width: 2,
46   - color: const Color(0xFF140C10),
47   - ),
48   - borderRadius: BorderRadius.circular(6)),
49   - child: OwImageWidget(
50   - name: unitLesson.coverUrl ?? '',
51   - fit: BoxFit.fitHeight,
  42 + decoration: BoxDecoration(
  43 + border: Border.all(
  44 + width: 2,
  45 + color: const Color(0xFF140C10),
52 46 ),
53   - )),
  47 + borderRadius: BorderRadius.circular(6)),
  48 + child: OwImageWidget(
  49 + name: unitLesson.coverUrl ?? '',
  50 + fit: BoxFit.fitHeight,
  51 + ),
  52 + )),
54 53 20.verticalSpace,
55 54 SizedBox(
56 55 height: 40.h,
... ... @@ -58,8 +57,7 @@ class CourseUnitItem extends StatelessWidget {
58 57 unitLesson.name ?? '',
59 58 maxLines: 2,
60 59 overflow: TextOverflow.ellipsis,
61   - style:
62   - TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)),
  60 + style: TextStyle(fontSize: 11.sp, color: const Color(0xFF140C10)),
63 61 ),
64 62 )
65 63 ],
... ... @@ -75,12 +73,8 @@ class CourseUnitItem extends StatelessWidget {
75 73 width: 165.w,
76 74 decoration: BoxDecoration(
77 75 image: DecorationImage(
78   - image: AssetImage(
79   - 'gendubeij_mengban'.assetPng
80   - ),
81   - fit: BoxFit.fill
82   - )
83   - ),
  76 + image: AssetImage('gendubeij_mengban'.assetPng),
  77 + fit: BoxFit.fill)),
84 78 alignment: Alignment.center,
85 79 child: Image.asset(
86 80 'iv_lock'.assetPng,
... ...
lib/pages/user/setting/reback_page.dart
... ... @@ -26,73 +26,87 @@ class ReBackPageState extends State&lt;ReBackPage&gt; {
26 26 @override
27 27 Widget build(BuildContext context) {
28 28 return Scaffold(
29   - appBar: const WEAppBar(
30   - titleText: '我要反馈',
31   - ),
32   - body: Container(
33   - color: Colors.white,
34   - padding: EdgeInsets.symmetric(
35   - horizontal: 24.w
  29 + appBar: const WEAppBar(
  30 + titleText: '我要反馈',
36 31 ),
37   - child: SafeArea(
38   - child: Column(
39   - children: [
40   - 20.verticalSpace,
41   - Row(
42   - mainAxisAlignment: MainAxisAlignment.spaceBetween,
43   - children: [
44   - Text(
45   - '请输入您要反馈的问题和意见,10-500个字',
46   - textAlign: TextAlign.left,
47   - style: TextStyle(
48   - fontSize: 19.sp,
49   - color: HexColor('#333333')
50   - ),
51   - ),
52   - Text(
53   - '48/500',
54   - textAlign: TextAlign.right,
55   - style: TextStyle(
56   - fontSize: 19.sp,
57   - color: HexColor('#333333')
58   - ),)
59   - ],
60   - ),
61   - 9.5.verticalSpace,
62   - Expanded(
63   - child: Container(
64   - decoration: BoxDecoration(
65   - image: DecorationImage(
66   - fit: BoxFit.fill,
67   - image: AssetImage('bg_reback'.assetPng)
  32 + body: Container(
  33 + color: Colors.white,
  34 + padding: EdgeInsets.symmetric(horizontal: 10.w),
  35 + child: SafeArea(
  36 + child: LayoutBuilder(builder: (context, constraints) {
  37 + return SingleChildScrollView(
  38 + child: ConstrainedBox(
  39 + constraints: BoxConstraints(
  40 + minHeight: constraints.maxHeight,
  41 + ),
  42 + child: IntrinsicHeight(
  43 + child: Column(
  44 + children: [
  45 + 20.verticalSpace,
  46 + Row(
  47 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  48 + children: [
  49 + Text(
  50 + '请输入您要反馈的问题和意见,10-500个字',
  51 + textAlign: TextAlign.left,
  52 + style: TextStyle(
  53 + fontSize: 19.sp, color: HexColor('#333333')),
  54 + ),
  55 + Text(
  56 + '48/500',
  57 + textAlign: TextAlign.right,
  58 + style: TextStyle(
  59 + fontSize: 19.sp, color: HexColor('#333333')),
  60 + )
  61 + ],
  62 + ),
  63 + 9.5.verticalSpace,
  64 + Expanded(
  65 + child: Container(
  66 + decoration: BoxDecoration(
  67 + image: DecorationImage(
  68 + image: AssetImage('bg_reback'.assetPng),
  69 + fit: BoxFit.fill)),
  70 + child: Padding(
  71 + padding: const EdgeInsets.symmetric(
  72 + vertical: 10, horizontal: 16),
  73 + // 设置对称内边距
  74 + child: TextField(
  75 + textInputAction: TextInputAction.done,
  76 + decoration: InputDecoration(
  77 + border: InputBorder.none,
  78 + hintStyle: TextStyle(
  79 + fontSize: 16.sp,
  80 + color: const Color(0xFF999999))),
  81 + ),
  82 + ),
  83 + ),
  84 + ),
  85 + 4.5.verticalSpace,
  86 + Container(
  87 + decoration: BoxDecoration(
  88 + image: DecorationImage(
  89 + fit: BoxFit.fill,
  90 + image: AssetImage(_canEnsure
  91 + ? 're_button'.assetPng
  92 + : 're_button_dis'.assetPng))),
  93 + alignment: Alignment.center,
  94 + width: 91.w,
  95 + height: 45.h,
  96 + child: Text(
  97 + '提交',
  98 + textAlign: TextAlign.center,
  99 + style:
  100 + TextStyle(color: Colors.white, fontSize: 17.sp),
  101 + ),
68 102 )
  103 + ],
69 104 ),
70 105 ),
71 106 ),
72   - 4.5.verticalSpace,
73   - Container(
74   - decoration: BoxDecoration(
75   - image: DecorationImage(
76   - fit: BoxFit.fill,
77   - image: AssetImage(_canEnsure?'re_button'.assetPng:'re_button_dis'.assetPng)
78   - )
79   - ),
80   - alignment: Alignment.center,
81   - width: 91.w,
82   - height: 45.h,
83   - child: Text(
84   - '提交',
85   - textAlign: TextAlign.center,
86   - style: TextStyle(
87   - color: Colors.white,
88   - fontSize: 17.sp
89   - ),
90   - ),
91   - )
92   - ],
93   - ),
  107 + );
  108 + }),
94 109 ),
95   - )
96   - );
  110 + ));
97 111 }
98   -}
99 112 \ No newline at end of file
  113 +}
... ...
lib/pages/user/setting/setting_page.dart
1 1 import 'package:flutter/material.dart';
2 2 import 'package:flutter_screenutil/flutter_screenutil.dart';
  3 +import 'package:package_info_plus/package_info_plus.dart';
3 4 import 'package:wow_english/common/widgets/we_app_bar.dart';
4 5  
5 6 import '../../../route/route.dart';
... ... @@ -9,14 +10,30 @@ class SettingPage extends StatefulWidget {
9 10  
10 11 @override
11 12 State<StatefulWidget> createState() {
12   - return SettingPageState();
  13 + return SettingPageState();
13 14 }
14 15 }
15 16  
16 17 class SettingPageState extends State<SettingPage> {
  18 + String? _version;
  19 + String? _buildNum;
  20 + @override
  21 + void initState() {
  22 + super.initState();
  23 + _retrieveVersionInfo();
  24 + }
  25 +
  26 + Future<void> _retrieveVersionInfo() async {
  27 + PackageInfo packageInfo = await PackageInfo.fromPlatform();
  28 + setState(() {
  29 + _version = packageInfo.version;
  30 + _buildNum = packageInfo.buildNumber;
  31 + });
  32 + }
  33 +
17 34 @override
18 35 Widget build(BuildContext context) {
19   - return Scaffold(
  36 + return Scaffold(
20 37 appBar: const WEAppBar(
21 38 titleText: '设置',
22 39 ),
... ... @@ -28,17 +45,18 @@ class SettingPageState extends State&lt;SettingPage&gt; {
28 45 child: ListView(
29 46 children: [
30 47 34.verticalSpace,
31   - _buildItemWidget('注销账号', onPress: (){
  48 + _buildItemWidget('注销账号', onPress: () {
32 49 pushNamed(AppRouteName.deleteAccount);
33 50 }),
34 51 12.verticalSpace,
35   - _buildItemWidget('清除缓存', onPress: (){
36   -
37   - }),
  52 + _buildItemWidget('清除缓存', onPress: () {}),
38 53 12.verticalSpace,
39   - _buildItemWidget('帮助与反馈', onPress: (){
  54 + _buildItemWidget('帮助与反馈', onPress: () {
40 55 pushNamed(AppRouteName.reBack);
41 56 }),
  57 + 12.verticalSpace,
  58 + _buildItemWidget('Version: $_version Build:$_buildNum',
  59 + onPress: () {}),
42 60 ],
43 61 ),
44 62 ),
... ... @@ -46,13 +64,15 @@ class SettingPageState extends State&lt;SettingPage&gt; {
46 64 ),
47 65 );
48 66 }
49   -
50   - Widget _buildItemWidget(String text,{VoidCallback? onPress}) {
  67 +
  68 + Widget _buildItemWidget(String text, {VoidCallback? onPress}) {
51 69 return OutlinedButton(
52 70 onPressed: () => onPress?.call(),
53 71 style: ButtonStyle(
54   - side: MaterialStateProperty.all(BorderSide(color: const Color(0xFF140C10), width: 1.5.w)),
55   - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.r))),
  72 + side: MaterialStateProperty.all(
  73 + BorderSide(color: const Color(0xFF140C10), width: 1.5.w)),
  74 + shape: MaterialStateProperty.all(
  75 + RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.r))),
56 76 minimumSize: MaterialStateProperty.all(Size(double.infinity, 58.h)),
57 77 backgroundColor: MaterialStateProperty.all(Colors.white),
58 78 ),
... ... @@ -66,4 +86,4 @@ class SettingPageState extends State&lt;SettingPage&gt; {
66 86 ),
67 87 );
68 88 }
69   -}
70 89 \ No newline at end of file
  90 +}
... ...
lib/pages/user/user_page.dart
... ... @@ -173,15 +173,7 @@ class _UserView extends StatelessWidget {
173 173 UserUtil.getUser()?.phoneNum == '17718485544')
174 174 ? 12.verticalSpace
175 175 : 1.verticalSpace),
176   - OutlinedButton(
177   - onPressed: () => pushNamed(AppRouteName.fogPwd),
178   - style: normalButtonStyle,
179   - child: Text(
180   - "修改密码",
181   - style: textStyle21sp,
182   - ),
183   - ),
184   - 12.verticalSpace,
  176 +
185 177 Offstage(
186 178 offstage: AppConfigHelper.shouldHidePay(),
187 179 child: OutlinedButton(
... ... @@ -199,6 +191,15 @@ class _UserView extends StatelessWidget {
199 191 style: textStyle21sp,
200 192 )),
201 193 ),
  194 + 12.verticalSpace,
  195 + OutlinedButton(
  196 + onPressed: () => pushNamed(AppRouteName.fogPwd),
  197 + style: normalButtonStyle,
  198 + child: Text(
  199 + "修改密码",
  200 + style: textStyle21sp,
  201 + ),
  202 + ),
202 203 Offstage(
203 204 offstage: AppConfigHelper.shouldHidePay(),
204 205 child: 12.verticalSpace,
... ...
lib/route/route.dart
... ... @@ -17,6 +17,7 @@ import &#39;package:wow_english/pages/repeatafter/repeat_after_page.dart&#39;;
17 17 import 'package:wow_english/pages/repeataftercontent/repeat_after_content_page.dart';
18 18 import 'package:wow_english/pages/shop/exchane/exchange_lesson_page.dart';
19 19 import 'package:wow_english/pages/shop/exchangelist/exchange_lesson_list_page.dart';
  20 +import 'package:wow_english/pages/shop/home/shop_desc_page.dart';
20 21 import 'package:wow_english/pages/shop/home/shop_home_page.dart';
21 22 import 'package:wow_english/pages/user/information/user_information_page.dart';
22 23 import 'package:wow_english/pages/user/modify/modify_user_avatar_page.dart';
... ... @@ -49,6 +50,7 @@ class AppRouteName {
49 50 static const String courseSection = 'courseSections';
50 51 static const String listen = 'listen';
51 52 static const String shop = 'shop';
  53 + static const String shopDesc = 'shopDesc';
52 54 static const String exLesson = 'exLesson';
53 55 static const String exList = 'exList';
54 56 static const String reAfter = 'reAfter';
... ... @@ -141,6 +143,8 @@ class AppRouter {
141 143 return CupertinoPageRoute(builder: (_) => const ListenPage());
142 144 case AppRouteName.shop:
143 145 return CupertinoPageRoute(builder: (_) => const ShopHomePage());
  146 + case AppRouteName.shopDesc:
  147 + return CupertinoPageRoute(builder: (_) => const ShopDescPage());
144 148 case AppRouteName.pay:
145 149 var productEntity = ProductEntity();
146 150 if (settings.arguments != null && settings.arguments is ProductEntity) {
... ...
lib/utils/audio_player_util.dart 0 → 100644
  1 +import 'package:audioplayers/audioplayers.dart';
  2 +import 'package:flutter/cupertino.dart';
  3 +import 'package:wow_english/common/extension/string_extension.dart';
  4 +
  5 +import 'log_util.dart';
  6 +
  7 +enum AudioPlayerUtilType {
  8 + welcomeToWow('welcome_to_wow'),
  9 + classTime('class_time'),
  10 + gameTime('game_time'),
  11 + musicTime('music_time'),
  12 + readingTime('reading_time'),
  13 + videoTime('video_time'),
  14 + quizTime('quiz_time'),
  15 + countWithMe('count_with_me_instrumental'),
  16 + inMyTummy('in_my_tummy_instrumental'),
  17 + touch('touch_instrumental');
  18 +
  19 + const AudioPlayerUtilType(this.path);
  20 +
  21 + final String path;
  22 +}
  23 +
  24 +class AudioPlayerUtil extends WidgetsBindingObserver {
  25 + static AudioPlayerUtil? _instance;
  26 + late AudioPlayer _audioPlayer;
  27 + late AudioPlayerUtilType currentType;
  28 + bool _wasPlaying = false;
  29 + static const TAG = "AudioPlayerUtil";
  30 +
  31 + // 私有构造函数
  32 + AudioPlayerUtil._internal() {
  33 + // 监听应用生命周期
  34 + WidgetsBinding.instance.addObserver(this);
  35 + _audioPlayer = AudioPlayer();
  36 + _audioPlayer.onPlayerStateChanged.listen((event) async {
  37 + if (event == PlayerState.completed) {
  38 + // 播放结束再次播放
  39 + if (currentType == AudioPlayerUtilType.inMyTummy) {
  40 + AudioPlayerUtil.getInstance()
  41 + .playAudio(AudioPlayerUtilType.inMyTummy);
  42 + }
  43 + if (currentType == AudioPlayerUtilType.countWithMe) {
  44 + AudioPlayerUtil.getInstance()
  45 + .playAudio(AudioPlayerUtilType.countWithMe);
  46 + }
  47 + if (currentType == AudioPlayerUtilType.welcomeToWow) {
  48 + AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.touch);
  49 + }
  50 + if (currentType == AudioPlayerUtilType.touch) {
  51 + AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.touch);
  52 + }
  53 + }
  54 + });
  55 + }
  56 +
  57 + static AudioPlayerUtil getInstance() {
  58 + _instance ??= AudioPlayerUtil._internal();
  59 + return _instance!;
  60 + }
  61 +
  62 +// 播放音频
  63 + Future<void> playAudio(AudioPlayerUtilType type) async {
  64 + Log.d("$TAG playAudio $type");
  65 + currentType = type;
  66 + String path = type.path;
  67 + await _audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5);
  68 + await _audioPlayer.onPlayerComplete.first;
  69 + }
  70 +
  71 + // stop
  72 + Future<void> stop() async {
  73 + Log.d("$TAG stop _audioPlayer.state=${_audioPlayer.state}");
  74 + await _audioPlayer.stop();
  75 + }
  76 +
  77 + // pause
  78 + Future<void> pause() async {
  79 + Log.d("$TAG pause _audioPlayer.state=${_audioPlayer.state}");
  80 + if (_audioPlayer.state == PlayerState.playing) {
  81 + await _audioPlayer.pause();
  82 + }
  83 + }
  84 +
  85 + // resume
  86 + Future<void> resume() async {
  87 + Log.d("$TAG resume _audioPlayer.state=${_audioPlayer.state}");
  88 + if (_audioPlayer.state == PlayerState.paused) {
  89 + await _audioPlayer.resume();
  90 + }
  91 + }
  92 +
  93 + @override
  94 + void didChangeAppLifecycleState(AppLifecycleState state) async {
  95 + Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer.state}");
  96 + if (state == AppLifecycleState.paused) {
  97 + if (_audioPlayer.state == PlayerState.playing) {
  98 + _wasPlaying = true;
  99 + await pause();
  100 + };
  101 + } else if (state == AppLifecycleState.resumed) {
  102 + if (_wasPlaying == true) {
  103 + _wasPlaying = false;
  104 + await resume();
  105 + }
  106 + }
  107 + }
  108 +
  109 + void dispose() {
  110 + Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer.state}");
  111 + _audioPlayer.dispose();
  112 + WidgetsBinding.instance.removeObserver(this);
  113 + }
  114 +}
... ...