Commit d28ecdb0e9746ff584adf98a17b045aabbe9353f

Authored by xiaoyu
2 parents ed94c3db 2a9895f6

Merge remote-tracking branch 'origin/feat-wqf-payment' into xiaoyu_cocossteve

android/app/build.gradle
... ... @@ -88,10 +88,11 @@ dependencies {
88 88  
89 89 // sing sound
90 90 implementation 'com.singsound.library:evaluating:2.1.9'
91   - implementation "com.google.code.gson:gson:2.9.0"
  91 + implementation "com.google.code.gson:gson:2.10"
92 92 // 基础依赖包,必须要依赖
93 93 implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
94 94 // kotlin扩展(可选)
95 95 implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2'
96   - implementation 'io.keyss.android.library:bjgame:1.0.3'
  96 + // coco2d游戏
  97 + implementation 'io.keyss.android.library:steve_game:1.0.0'
97 98 }
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt
1 1 package com.kouyuxingqiu.wow_english.methodChannels
2 2  
3 3 import android.content.Intent
4   -import android.content.Intent.getIntent
5 4 import android.util.Log
6   -import androidx.core.content.ContextCompat.startActivity
7   -import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper
8   -import com.kouyuxingqiu.wow_english.util.GlobalHandler
9 5 import io.flutter.embedding.android.FlutterActivity
10 6 import io.flutter.embedding.engine.FlutterEngine
11 7 import io.flutter.plugin.common.MethodChannel
... ... @@ -45,7 +41,7 @@ class GameMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEngine)
45 41 val gameId = call.argument<Int>("gameId")
46 42 activity.startActivity(
47 43 Intent(activity, AppActivity::class.java).apply {
48   - putExtra("game", gameId)
  44 + putExtra("gameId", gameId)
49 45 })
50 46 result.success(true)
51 47 } else {
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
... ... @@ -8,7 +8,6 @@ import com.kouyuxingqiu.wow_english.util.GlobalHandler
8 8 import io.flutter.embedding.android.FlutterActivity
9 9 import io.flutter.embedding.engine.FlutterEngine
10 10 import io.flutter.plugin.common.MethodChannel
11   -import org.json.JSONObject
12 11 import java.lang.ref.WeakReference
13 12  
14 13 /**
... ...
lib/app/splash_page.dart
... ... @@ -16,6 +16,9 @@ import &#39;package:wow_english/route/route.dart&#39;;
16 16 import 'package:wow_english/utils/log_util.dart';
17 17 import 'package:wow_english/utils/sp_util.dart';
18 18  
  19 +import '../common/core/app_consts.dart';
  20 +import '../common/widgets/webview_dialog.dart';
  21 +
19 22 class SplashPage extends StatelessWidget {
20 23 const SplashPage({super.key});
21 24  
... ... @@ -72,7 +75,33 @@ class _TransitionViewState extends State&lt;TransitionView&gt; {
72 75 } else {
73 76 pushNamedAndRemoveUntil(AppRouteName.login, (route) => false);
74 77 }*/
75   - pushNamedAndRemoveUntil(AppRouteName.moduleSelect, (route) => false);
  78 + bool isAggreementAccepted = AppConfigHelper.getAgreementAccepted();
  79 + if (isAggreementAccepted) {
  80 + pushNamedAndRemoveUntil(AppRouteName.moduleSelect, (route) => false);
  81 + } else {
  82 + showDialog(
  83 + context: context,
  84 + barrierDismissible: false,
  85 + builder: (BuildContext context) {
  86 + return WillPopScope(
  87 + onWillPop: () => Future.value(false),
  88 + child: WebviewDialog(
  89 + title: "服务条款及隐私政策",
  90 + webUrl: AppConsts.userPrivacyPolicyUrl,
  91 + leftTap: () {
  92 + AppConfigHelper.saveAgreementAccepted(true);
  93 + pushNamedAndRemoveUntil(
  94 + AppRouteName.moduleSelect, (route) => false);
  95 + },
  96 + rightTap: () {
  97 + // 退出应用
  98 + SystemNavigator.pop();
  99 + },
  100 + ),
  101 + );
  102 + },
  103 + );
  104 + }
76 105 });
77 106 }
78 107  
... ...
lib/common/core/app_config_helper.dart
... ... @@ -3,21 +3,26 @@ import &#39;dart:io&#39;;
3 3  
4 4 import 'package:flutter/cupertino.dart';
5 5 import 'package:package_info_plus/package_info_plus.dart';
  6 +import 'package:wow_english/common/core/sp_const.dart';
6 7 import 'package:wow_english/common/core/user_util.dart';
7 8  
8 9 import '../../models/app_config_entity.dart';
  10 +import '../../utils/sp_util.dart';
9 11 import '../request/dao/system_dao.dart';
10 12  
11 13 class AppConfigHelper {
12 14  
13   - static AppConfigEntityEntity? configEntityEntity;
  15 + static AppConfigEntity? configEntityEntity;
14 16  
15   - static String versionCode = '';
  17 + static String _versionCode = '';
16 18  
17   - /// 获取用户信息
18   - static Future<Void?> getAppConfig() async {
  19 + // 获取用户信息
  20 + static Future<AppConfigEntity?> getAppConfig() async {
  21 + if (configEntityEntity != null) {
  22 + return configEntityEntity;
  23 + }
19 24 configEntityEntity = await SystemDao.getAppConfig();
20   - return null;
  25 + return configEntityEntity;
21 26 }
22 27  
23 28 // 是否需要隐藏...
... ... @@ -27,15 +32,27 @@ class AppConfigHelper {
27 32  
28 33 // 获取app版本号
29 34 static Future<String> getAppVersion() async {
30   - if (versionCode.isNotEmpty) {
31   - return versionCode;
  35 + if (_versionCode.isNotEmpty) {
  36 + return _versionCode;
32 37 }
33 38 PackageInfo packageInfo = await PackageInfo.fromPlatform();
34   - String version = packageInfo.version; // 版本号
35   - String buildNumber = packageInfo.buildNumber; // 构建号
36   - versionCode = version;
  39 + String versionName = packageInfo.version; // 版本号
  40 + String versionCode = packageInfo.buildNumber; // 构建号
  41 + _versionCode = versionCode;
37 42  
38   - debugPrint('versionCode=$versionCode platForm=${Platform.operatingSystem}');
  43 + debugPrint('versionName=$versionName versionCode=$versionCode platForm=${Platform.operatingSystem}');
39 44 return versionCode;
40 45 }
  46 +
  47 + static void saveAgreementAccepted(bool accepted) {
  48 + SpUtil.getInstance().setData(SpConst.prefsKeyAgreementAccepted, accepted);
  49 + }
  50 +
  51 + static bool getAgreementAccepted() {
  52 + return SpUtil.getInstance().get<bool>(SpConst.prefsKeyAgreementAccepted) ?? false;
  53 + }
  54 +
  55 + static void _clearUserData() {
  56 + SpUtil.getInstance().remove(SpConst.prefsKeyAgreementAccepted);
  57 + }
41 58 }
... ...
lib/common/core/sp_const.dart
1 1 class SpConst {
2 2 static const String prefsKeyUserInfo = "key_user_info";
  3 +
  4 + static const String prefsKeyAgreementAccepted = "privacy_agreement_accepted";
3 5 }
... ...
lib/common/core/user_util.dart
... ... @@ -65,6 +65,7 @@ class UserUtil {
65 65  
66 66 // 是否有游戏权限
67 67 static bool hasGamePermission() {
  68 + debugPrint('hasGamePermission: ${_userEntity}');
68 69 return _userEntity?.valid ?? false;
69 70 }
70 71  
... ...
lib/common/request/dao/shop_dao.dart
... ... @@ -8,20 +8,20 @@ class ShopDao {
8 8 }
9 9  
10 10 ///创建订单
11   - static Future createOrder(ProductEntity productEntity) async {
  11 + static Future<Map<String, dynamic>?> createOrder(ProductEntity productEntity) async {
12 12 return await requestClient
13   - .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id});
  13 + .post<Map<String, dynamic>?>(Apis.createOrder, data: {'courseComboId': productEntity.id});
14 14 }
15 15  
16 16 ///获取alipay支付订单信息
17   - static Future getAliPayToken(String orderNo) async {
  17 + static Future<Map<String, dynamic>?> getAliPayToken(String orderNo) async {
18 18 return await requestClient
19   - .post<Map<String, dynamic>>(Apis.getAliPayToken, data: {'orderNo': orderNo});
  19 + .post<Map<String, dynamic>?>(Apis.getAliPayToken, data: {'orderNo': orderNo});
20 20 }
21 21  
22 22 ///获取weixin支付订单信息
23   - static Future getWxPayToken(String orderNo) async {
  23 + static Future<Map<String, dynamic>?> getWxPayToken(String orderNo) async {
24 24 return await requestClient
25   - .post<Map<String, dynamic>>(Apis.getWxPayToken, data: {'orderNo': orderNo});
  25 + .post<Map<String, dynamic>?>(Apis.getWxPayToken, data: {'orderNo': orderNo});
26 26 }
27 27 }
... ...
lib/common/request/dao/system_dao.dart
... ... @@ -4,7 +4,7 @@ import &#39;../request_client.dart&#39;;
4 4 class SystemDao {
5 5  
6 6 /// 获取配置信息
7   - static Future<AppConfigEntityEntity?> getAppConfig() async {
  7 + static Future<AppConfigEntity?> getAppConfig() async {
8 8 return await requestClient.get(Apis.appConfig);
9 9 }
10 10 }
... ...
lib/common/widgets/webview_dialog.dart 0 → 100644
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter_easyloading/flutter_easyloading.dart';
  3 +import 'package:webview_flutter/webview_flutter.dart';
  4 +
  5 +class WebviewDialog extends StatelessWidget {
  6 + final String title;
  7 + final String webUrl;
  8 + final VoidCallback leftTap;
  9 + final VoidCallback rightTap;
  10 +
  11 + const WebviewDialog(
  12 + {super.key,
  13 + required this.title,
  14 + required this.webUrl,
  15 + required this.leftTap,
  16 + required this.rightTap});
  17 +
  18 + @override
  19 + Widget build(BuildContext context) {
  20 + return AlertDialog(
  21 + title: Center(
  22 + child: Text(title),
  23 + ),
  24 + content: SizedBox(
  25 + width: MediaQuery.of(context).size.height - 100,
  26 + height: MediaQuery.of(context).size.width - 100,
  27 + child: FutureBuilder(
  28 + // 异步方法
  29 + future: buildWebViewController(webUrl),
  30 + builder: (context, snapshot) {
  31 + // 等待状态显示的widget
  32 + if (snapshot.connectionState == ConnectionState.waiting) {
  33 + return const Center(
  34 + child: CircularProgressIndicator(),
  35 + );
  36 + // 错误时显示的widget
  37 + } else if (snapshot.hasError) {
  38 + return const Text('Error');
  39 + } else {
  40 + return snapshot.data ?? const Text('No data');
  41 + }
  42 + })),
  43 + actions: <Widget>[
  44 + TextButton(
  45 + child:
  46 + const Text('同意并继续', style: TextStyle(color: Color(0xFFFBB621))),
  47 + onPressed: () {
  48 + // 处理接受按钮的点击事件
  49 + leftTap(); // 关闭对话框
  50 + },
  51 + ),
  52 + TextButton(
  53 + child: const Text('不同意,退出应用'),
  54 + onPressed: () {
  55 + // 处理拒绝按钮的点击事件
  56 + rightTap(); // 关闭对话框
  57 + },
  58 + ),
  59 + ],
  60 + );
  61 + }
  62 +
  63 + Future<Widget> buildWebViewController(String url) async {
  64 + Widget res;
  65 + try {
  66 + WebViewController controller = WebViewController()
  67 + ..setJavaScriptMode(JavaScriptMode.unrestricted)
  68 + ..setBackgroundColor(const Color(0x00000000))
  69 + ..setNavigationDelegate(
  70 + NavigationDelegate(
  71 + onProgress: (int progress) {
  72 + // Update loading bar.
  73 + },
  74 + onPageStarted: (String url) {
  75 + EasyLoading.show();
  76 + },
  77 + onPageFinished: (String url) {
  78 + EasyLoading.dismiss();
  79 + },
  80 + onWebResourceError: (WebResourceError error) {
  81 + EasyLoading.showError(error.description);
  82 + },
  83 + onNavigationRequest: (NavigationRequest request) {
  84 + return NavigationDecision.navigate;
  85 + },
  86 + ),
  87 + );
  88 + await controller.loadRequest(Uri.parse(url));
  89 + res = WebViewWidget(controller: controller);
  90 + } catch (error) {
  91 + res = Text("加载失败:${error.toString()}");
  92 + debugPrint("WebViewController加载失败:${error.toString()}");
  93 + }
  94 + return res;
  95 + }
  96 +}
... ...
lib/generated/json/app_config_entity.g.dart
1 1 import 'package:wow_english/generated/json/base/json_convert_content.dart';
2 2 import 'package:wow_english/models/app_config_entity.dart';
3 3  
4   -AppConfigEntityEntity $AppConfigEntityEntityFromJson(
  4 +AppConfigEntity $AppConfigEntityEntityFromJson(
5 5 Map<String, dynamic> json) {
6   - final AppConfigEntityEntity appConfigEntityEntity = AppConfigEntityEntity();
  6 + final AppConfigEntity appConfigEntityEntity = AppConfigEntity();
7 7 final bool? androidForceUpdate = jsonConvert.convert<bool>(
8 8 json['androidForceUpdate']);
9 9 if (androidForceUpdate != null) {
... ... @@ -50,7 +50,7 @@ AppConfigEntityEntity $AppConfigEntityEntityFromJson(
50 50 }
51 51  
52 52 Map<String, dynamic> $AppConfigEntityEntityToJson(
53   - AppConfigEntityEntity entity) {
  53 + AppConfigEntity entity) {
54 54 final Map<String, dynamic> data = <String, dynamic>{};
55 55 data['androidForceUpdate'] = entity.androidForceUpdate;
56 56 data['androidRecommendUpdate'] = entity.androidRecommendUpdate;
... ... @@ -64,8 +64,8 @@ Map&lt;String, dynamic&gt; $AppConfigEntityEntityToJson(
64 64 return data;
65 65 }
66 66  
67   -extension AppConfigEntityEntityExtension on AppConfigEntityEntity {
68   - AppConfigEntityEntity copyWith({
  67 +extension AppConfigEntityEntityExtension on AppConfigEntity {
  68 + AppConfigEntity copyWith({
69 69 bool? androidForceUpdate,
70 70 bool? androidRecommendUpdate,
71 71 String? androidUpdatePackageUrl,
... ... @@ -76,7 +76,7 @@ extension AppConfigEntityEntityExtension on AppConfigEntityEntity {
76 76 String? noticeBeforePurchaseUrl,
77 77 String? safe,
78 78 }) {
79   - return AppConfigEntityEntity()
  79 + return AppConfigEntity()
80 80 ..androidForceUpdate = androidForceUpdate ?? this.androidForceUpdate
81 81 ..androidRecommendUpdate = androidRecommendUpdate ??
82 82 this.androidRecommendUpdate
... ...
lib/generated/json/base/json_convert_content.dart
... ... @@ -155,9 +155,9 @@ class JsonConvert {
155 155 Map<String, dynamic> e) =>
156 156 AliyunOssUploadStsCallbackParam.fromJson(e)).toList() as M;
157 157 }
158   - if (<AppConfigEntityEntity>[] is M) {
159   - return data.map<AppConfigEntityEntity>((Map<String, dynamic> e) =>
160   - AppConfigEntityEntity.fromJson(e)).toList() as M;
  158 + if (<AppConfigEntity>[] is M) {
  159 + return data.map<AppConfigEntity>((Map<String, dynamic> e) =>
  160 + AppConfigEntity.fromJson(e)).toList() as M;
161 161 }
162 162 if (<CourseEntity>[] is M) {
163 163 return data.map<CourseEntity>((Map<String, dynamic> e) =>
... ... @@ -236,7 +236,7 @@ class JsonConvertClassCollection {
236 236 (AliyunOssUploadStsEntity).toString(): AliyunOssUploadStsEntity.fromJson,
237 237 (AliyunOssUploadStsCallbackParam)
238 238 .toString(): AliyunOssUploadStsCallbackParam.fromJson,
239   - (AppConfigEntityEntity).toString(): AppConfigEntityEntity.fromJson,
  239 + (AppConfigEntity).toString(): AppConfigEntity.fromJson,
240 240 (CourseEntity).toString(): CourseEntity.fromJson,
241 241 (CourseCourseLessons).toString(): CourseCourseLessons.fromJson,
242 242 (CourseModuleEntity).toString(): CourseModuleEntity.fromJson,
... ...
lib/models/app_config_entity.dart
... ... @@ -5,7 +5,7 @@ import &#39;../generated/json/app_config_entity.g.dart&#39;;
5 5  
6 6  
7 7 @JsonSerializable()
8   -class AppConfigEntityEntity {
  8 +class AppConfigEntity {
9 9  
10 10 // 安卓是否强制更新
11 11 bool? androidForceUpdate;
... ... @@ -17,14 +17,17 @@ class AppConfigEntityEntity {
17 17 String? androidUpdatePackageUrl;
18 18  
19 19 // 安卓当前版本号
20   - int? androidVersion;
  20 + late int androidVersion;
21 21  
22 22 bool? iosForceUpdate;
23 23  
24 24 bool? iosRecommendUpdate;
25 25  
26 26 // ios版本
27   - int? iosVersion;
  27 + late int iosVersion;
  28 +
  29 + // 更新说明
  30 + String? updatePackageDescription;
28 31  
29 32 // 购前须知图片
30 33 String? noticeBeforePurchaseUrl;
... ... @@ -33,9 +36,9 @@ class AppConfigEntityEntity {
33 36 String? safe;
34 37  
35 38  
36   - AppConfigEntityEntity();
  39 + AppConfigEntity();
37 40  
38   - factory AppConfigEntityEntity.fromJson(Map<String, dynamic> json) => $AppConfigEntityEntityFromJson(json);
  41 + factory AppConfigEntity.fromJson(Map<String, dynamic> json) => $AppConfigEntityEntityFromJson(json);
39 42  
40 43 Map<String, dynamic> toJson() => $AppConfigEntityEntityToJson(this);
41 44  
... ...
lib/models/user_entity.dart
... ... @@ -63,6 +63,11 @@ class UserEntity {
63 63 : '女';
64 64 }
65 65  
  66 + // 是否有游戏权限
  67 + bool hasGamePermission() {
  68 + return valid ?? false;
  69 + }
  70 +
66 71 UserEntity copyWith({
67 72 int? id,
68 73 String? name,
... ...
lib/pages/moduleSelect/bloc.dart
1 1 import 'package:bloc/bloc.dart';
  2 +import 'package:flutter/cupertino.dart';
  3 +import 'package:flutter/foundation.dart';
  4 +import 'package:wow_english/models/app_config_entity.dart';
2 5  
  6 +import '../../common/core/app_config_helper.dart';
3 7 import 'event.dart';
4 8 import 'state.dart';
5 9  
6   -
7 10 class ModuleSelectBloc extends Bloc<ModuleSelectEvent, ModuleSelectState> {
8   -
9 11 ModuleSelectBloc() : super(ModuleSelectState().init()) {
10 12 on<InitEvent>(_init);
11 13 }
12 14  
13 15 void _init(InitEvent event, Emitter<ModuleSelectState> emit) async {
14   - emit(state.clone());
  16 + await _checkUpdate(emit);
  17 + debugPrint('WQF ModuleSelectBloc _init');
  18 + }
  19 +
  20 + Future<void> _checkUpdate(Emitter<ModuleSelectState> emit) async {
  21 + int localVersion = int.parse(await AppConfigHelper.getAppVersion());
  22 + AppConfigEntity? appConfigEntity = await AppConfigHelper.getAppConfig();
  23 + if (appConfigEntity == null) {
  24 + return;
  25 + }
  26 + debugPrint('WQF _checkUpdate');
  27 + if (defaultTargetPlatform == TargetPlatform.iOS) {
  28 + if (localVersion < appConfigEntity.iosVersion &&
  29 + appConfigEntity.iosRecommendUpdate == true) {
  30 + emit(UpdateDialogState(
  31 + appConfigEntity.iosForceUpdate ?? false, appConfigEntity));
  32 + }
  33 + } else {
  34 + if (localVersion < appConfigEntity.androidVersion &&
  35 + appConfigEntity.androidRecommendUpdate == true) {
  36 + emit(UpdateDialogState(
  37 + appConfigEntity.androidForceUpdate ?? false, appConfigEntity,
  38 + ));
  39 + }
  40 + }
15 41 }
16 42 }
... ...
lib/pages/moduleSelect/state.dart
  1 +import '../../models/app_config_entity.dart';
  2 +
1 3 class ModuleSelectState {
2 4 ModuleSelectState init() {
3 5 return ModuleSelectState();
... ... @@ -7,3 +9,12 @@ class ModuleSelectState {
7 9 return ModuleSelectState();
8 10 }
9 11 }
  12 +
  13 +class UpdateDialogState extends ModuleSelectState {
  14 +
  15 + final AppConfigEntity appConfigEntity;
  16 +
  17 + final bool forceUpdate;
  18 +
  19 + UpdateDialogState(this.forceUpdate, this.appConfigEntity);
  20 +}
... ...
lib/pages/moduleSelect/view.dart
  1 +
  2 +import 'package:flutter/foundation.dart';
1 3 import 'package:flutter/material.dart';
  4 +import 'package:flutter/services.dart';
  5 +import 'package:flutter_app_update/azhon_app_update.dart';
  6 +import 'package:flutter_app_update/update_model.dart';
2 7 import 'package:flutter_bloc/flutter_bloc.dart';
  8 +import 'package:url_launcher/url_launcher.dart';
3 9 import 'package:wow_english/common/core/app_config_helper.dart';
4 10 import 'package:wow_english/common/extension/string_extension.dart';
5 11 import 'package:wow_english/pages/moduleSelect/state.dart';
6 12 import 'package:wow_english/pages/moduleSelect/widgets/BaseHomeHeaderWidget.dart';
  13 +import 'package:wow_english/pages/user/bloc/user_bloc.dart';
7 14  
8 15 import '../../common/core/user_util.dart';
9 16 import '../../common/dialogs/show_dialog.dart';
  17 +import '../../models/app_config_entity.dart';
10 18 import 'bloc.dart';
11 19 import 'event.dart';
12 20 import 'package:flutter_screenutil/flutter_screenutil.dart';
... ... @@ -18,7 +26,9 @@ class ModuleSelectPage extends StatelessWidget {
18 26 @override
19 27 Widget build(BuildContext context) {
20 28 return BlocProvider(
21   - create: (BuildContext context) => ModuleSelectBloc()..add(InitEvent()),
  29 + create: (BuildContext context) =>
  30 + ModuleSelectBloc()
  31 + ..add(InitEvent()),
22 32 child: Builder(builder: (context) => _HomePageView()),
23 33 );
24 34 }
... ... @@ -27,106 +37,201 @@ class ModuleSelectPage extends StatelessWidget {
27 37 class _HomePageView extends StatelessWidget {
28 38 @override
29 39 Widget build(BuildContext context) {
30   - final bloc = BlocProvider.of<ModuleSelectBloc>(context);
31   - return BlocListener<ModuleSelectBloc, ModuleSelectState>(
32   - listener: (context, state) {},
33   - child: _homeView(),
34   - );
  40 + return MultiBlocListener(listeners: [
  41 + BlocListener<UserBloc, UserState>(listener: (context, state) {
  42 + debugPrint('WQF ModuleSelectPage BlocListener state: $state');
  43 + }),
  44 + BlocListener<ModuleSelectBloc, ModuleSelectState>(
  45 + listener: (context, state) {
  46 + if (state is UpdateDialogState) {
  47 + _showUpdateDialog(context, state.forceUpdate, state.appConfigEntity);
  48 + }
  49 + },
  50 + ),
  51 + ], child: _homeView());
35 52 }
36 53  
37   - Widget _homeView() => BlocBuilder<ModuleSelectBloc, ModuleSelectState>(
  54 + Widget _homeView() =>
  55 + BlocBuilder<ModuleSelectBloc, ModuleSelectState>(
38 56 builder: (context, state) {
39   - final bloc = BlocProvider.of<ModuleSelectBloc>(context);
40   - return Scaffold(
41   - body: Container(
42   - color: Colors.white,
43   - child: Column(
44   - children: [
45   - const BaseHomeHeaderWidget(),
46   - Expanded(
47   - child: Center(
48   - child: Row(
49   - children: [
50   - Expanded(
51   - child: GestureDetector(
52   - onTap: () {
53   - if (UserUtil.isLogined()) {
54   - pushNamed(AppRouteName.home);
55   - } else {
56   - pushNamed(AppRouteName.login);
57   - }
58   - },
59   - child: Column(
60   - mainAxisAlignment: MainAxisAlignment.center,
61   - children: [
62   - Stack(
63   - alignment: AlignmentDirectional.center,
64   - children: [
65   - Image.asset('bg_frame_module'.assetPng,
66   - width: 162.5.w, height: 203.5.h),
67   - Center(
68   - child: Image.asset(
69   - 'pic_module_study'.assetPng,
70   - width: 140.5.w,
71   - height: 172.h),
72   - )
73   - ]),
74   - 10.verticalSpace,
75   - Image.asset('label_module_study'.assetPng,
76   - width: 124.w, height: 34.h),
77   - ],
78   - ),
79   - ),
80   - ),
81   - Expanded(
82   - child: GestureDetector(
83   - onTap: () {
84   - //如果没登录先登录
85   - if (UserUtil.isLogined()) {
86   - if (AppConfigHelper.shouldHidePay()) {
87   - pushNamed(AppRouteName.games);
  57 + return Scaffold(
  58 + body: Container(
  59 + color: Colors.white,
  60 + child: Column(
  61 + children: [
  62 + const BaseHomeHeaderWidget(),
  63 + Expanded(
  64 + child: Center(
  65 + child: Row(
  66 + children: [
  67 + Expanded(
  68 + child: GestureDetector(
  69 + onTap: () {
  70 + if (UserUtil.isLogined()) {
  71 + pushNamed(AppRouteName.home);
88 72 } else {
89   - if (UserUtil.hasGamePermission()) {
90   - pushNamed(AppRouteName.games);
91   - } else {
92   - showTwoActionDialog(
93   - '提示', '忽略', '去续费',
94   - '您的课程已到期,请快快续费继续学习吧!', leftTap: () {
95   - popPage();
96   - }, rightTap: () {
97   - popPage();
98   - pushNamed(AppRouteName.shop);
99   - });
100   - }
  73 + pushNamed(AppRouteName.login);
101 74 }
102   - } else {
103   - pushNamed(AppRouteName.login);
104   - }
105   - },
106   - child: Column(
107   - mainAxisAlignment: MainAxisAlignment.center,
108   - children: [
109   - Stack(
110   - alignment: AlignmentDirectional.center,
111   - children: [
112   - Image.asset('bg_frame_module'.assetPng,
113   - width: 162.5.w, height: 203.5.h),
114   - Image.asset('pic_module_game'.assetPng,
115   - width: 140.5.w, height: 172.h)
116   - ]),
117   - 10.verticalSpace,
118   - Image.asset('label_module_game'.assetPng,
119   - width: 124.w, height: 34.h),
120   - ],
121   - )),
  75 + },
  76 + child: Column(
  77 + mainAxisAlignment: MainAxisAlignment.center,
  78 + children: [
  79 + Stack(
  80 + alignment: AlignmentDirectional.center,
  81 + children: [
  82 + Image.asset(
  83 + 'bg_frame_module'.assetPng,
  84 + width: 162.5.w, height: 203.5.h),
  85 + Center(
  86 + child: Image.asset(
  87 + 'pic_module_study'.assetPng,
  88 + width: 140.5.w,
  89 + height: 172.h),
  90 + )
  91 + ]),
  92 + 10.verticalSpace,
  93 + Image.asset('label_module_study'.assetPng,
  94 + width: 124.w, height: 34.h),
  95 + ],
  96 + ),
  97 + ),
  98 + ),
  99 + Expanded(
  100 + child: BlocBuilder<UserBloc, UserState>(
  101 + builder: (context, userState) {
  102 + debugPrint(
  103 + 'WQF ModuleSelectPage BlocBuilder state: $userState');
  104 + return GestureDetector(
  105 + onTap: () {
  106 + //如果没登录先登录
  107 + if (UserUtil.isLogined()) {
  108 + if (AppConfigHelper
  109 + .shouldHidePay()) {
  110 + pushNamed(AppRouteName.games);
  111 + } else {
  112 + if (UserUtil
  113 + .hasGamePermission()) {
  114 + pushNamed(AppRouteName.games);
  115 + } else {
  116 + showTwoActionDialog(
  117 + '提示', '忽略', '去续费',
  118 + '您的课程已到期,请快快续费继续学习吧!',
  119 + leftTap: () {
  120 + popPage();
  121 + }, rightTap: () {
  122 + popPage();
  123 + pushNamed(AppRouteName.shop);
  124 + });
  125 + }
  126 + }
  127 + } else {
  128 + pushNamed(AppRouteName.login);
  129 + }
  130 + },
  131 + child: Column(
  132 + mainAxisAlignment: MainAxisAlignment
  133 + .center,
  134 + children: [
  135 + Stack(
  136 + alignment: AlignmentDirectional
  137 + .center,
  138 + children: [
  139 + Image.asset(
  140 + 'bg_frame_module'
  141 + .assetPng,
  142 + width: 162.5.w,
  143 + height: 203.5.h),
  144 + Image.asset(
  145 + 'pic_module_game'
  146 + .assetPng,
  147 + width: 140.5.w,
  148 + height: 172.h)
  149 + ]),
  150 + 10.verticalSpace,
  151 + Image.asset(
  152 + 'label_module_game'.assetPng,
  153 + width: 124.w, height: 34.h),
  154 + ],
  155 + ));
  156 + }),
  157 + ),
  158 + ],
122 159 ),
123   - ],
124   - ),
125   - ),
126   - )
127   - ],
128   - ),
  160 + ),
  161 + )
  162 + ],
  163 + ),
  164 + ),
  165 + );
  166 + });
  167 +
  168 +
  169 + ///Flutter侧处理升级对话框
  170 + ///[forcedUpgrade] 是否强制升级
  171 + _showUpdateDialog(BuildContext context, bool forcedUpgrade,
  172 + AppConfigEntity appConfigEntity) {
  173 + showDialog(
  174 + context: context,
  175 + // 当我们点击除开对话框内容以外的区域是否关闭对话需用用到barrierDismissible参数 . 这个参数默认值是true ,但不能为null .
  176 + barrierDismissible: !forcedUpgrade,
  177 + builder: (BuildContext context) {
  178 + return WillPopScope(
  179 + onWillPop: () => Future.value(!forcedUpgrade),
  180 + child: AlertDialog(
  181 + title: const Text('发现新版本'),
  182 + content: Text(
  183 + appConfigEntity.updatePackageDescription ??
  184 + '修复了一些已知问题'),
  185 + actions: <Widget>[
  186 + TextButton(
  187 + child: Text(forcedUpgrade ? '退出' : '取消'),
  188 + onPressed: () =>
  189 + {
  190 + if (forcedUpgrade) {
  191 + SystemNavigator.pop()
  192 + } else
  193 + {
  194 + Navigator.of(context).pop()
  195 + }
  196 + },
  197 + ),
  198 + TextButton(
  199 + child: const Text('升级'),
  200 + onPressed: () async {
  201 + if (defaultTargetPlatform == TargetPlatform.iOS) {
  202 + _launchAppStore("6450870731");
  203 + return;
  204 + }
  205 + final String? apkUrl = appConfigEntity.androidUpdatePackageUrl;
  206 + if (apkUrl == null || apkUrl.isEmpty) {
  207 + return;
  208 + }
  209 + UpdateModel model = UpdateModel(
  210 + apkUrl,
  211 + "wowenglish.apk",
  212 + "ic_launcher",
  213 + '',
  214 + );
  215 + AzhonAppUpdate.update(model).then((value) =>
  216 + debugPrint('$value'));
  217 + if (!forcedUpgrade) {
  218 + Navigator.of(context).pop();
  219 + }
  220 + },
  221 + ),
  222 + ],
129 223 ),
130 224 );
131   - });
  225 + },
  226 + );
  227 + }
  228 +
  229 + void _launchAppStore(String appId) async {
  230 + final String url = 'https://apps.apple.com/cn/app/wow-english/id$appId';
  231 + if (await canLaunchUrl(Uri.parse(url))) {
  232 + await launchUrl(Uri.parse(url));
  233 + } else {
  234 + throw 'Could not launch $url';
  235 + }
  236 + }
132 237 }
... ...
lib/pages/shopping/bloc.dart
... ... @@ -36,6 +36,9 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
36 36 on<InitEvent>(_init);
37 37 on<ChangePaymentChannelEvent>(_changePaymentChannel);
38 38 on<DoPayEvent>(_startPay);
  39 + on<WxPaySuccessEvent>((event, emit) {
  40 + emit(PaySuccessState());
  41 + });
39 42 }
40 43  
41 44 void _init(InitEvent event, Emitter<ShoppingState> emit) async {
... ... @@ -55,7 +58,6 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
55 58  
56 59 void _startPay(DoPayEvent event,
57 60 Emitter<ShoppingState> emitter) async {
58   - Log.d("开始支付 ${event.productEntity} ${event.paymentChannel}");
59 61 //如果event.productEntity为空,中断流程并toast提示
60 62 if (event.productEntity == null) {
61 63 showToast("商品信息为空");
... ... @@ -64,29 +66,31 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
64 66 final productEntitySafely = event.productEntity!;
65 67 try {
66 68 await loading(() async {
67   - final Map<String, dynamic> orderInfo = await ShopDao.createOrder(productEntitySafely);
68   - Log.d("orderInfo $orderInfo");
69   - final String? orderNo = orderInfo.getOrNull("orderNo");
  69 + final Map<String, dynamic>? orderInfo = await ShopDao.createOrder(productEntitySafely);
  70 + final String? orderNo = orderInfo?.getOrNull("orderNo");
70 71 if (orderNo == null) {
71 72 showToast("订单创建失败");
72 73 return;
73 74 }
74   - Log.d("orderNo $orderNo");
75 75  
76 76 if (event.paymentChannel == PaymentChannel.wechatPay) {
77 77 if (_isWxPayListenerInitialized == false) {
78 78 _isWxPayListenerInitialized = true;
79 79 fluwx = Fluwx();
80   - fluwx?.registerApi(appId: "wx365e5a79956a450a",
  80 + await fluwx?.registerApi(appId: "wx365e5a79956a450a",
81 81 universalLink: "https://app-api.wowenglish.com.cn/app/");
82 82 wxPayResponseListener = (WeChatResponse response) {
83   - Log.d("wxPayResponseListener $response");
  83 + debugPrint("WqfPay wxPayResponseListener $response");
84 84 if (response is WeChatPaymentResponse) {
85 85 if (response.errCode == 0) {
86   - Log.d("wxPayResponseListener response=${response.errCode}");
  86 + debugPrint("WqfPay wxPayResponseListener response=${response.errCode}");
87 87 showToast("支付成功");
88 88 // Log.d("emitter isDone=${emitter.isDone}");
  89 +
  90 + /// 报错!_isCompleted emit was called after an event handler completed normally
89 91 // emitter(PaySuccessState());
  92 +
  93 + add(WxPaySuccessEvent());
90 94 } else {
91 95 showToast("支付失败");
92 96 }
... ... @@ -103,10 +107,8 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
103 107 return;
104 108 }
105 109  
106   - final Map<String, dynamic> wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo);
107   - Log.d("wxPayOrderInfo=$wxPayOrderInfo type=${wxPayOrderInfo.runtimeType}");
108   - final String? wxPayInfo = wxPayOrderInfo.getOrNull("appId");
109   - if (wxPayInfo == null) {
  110 + final Map<String, dynamic>? wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo);
  111 + if (wxPayOrderInfo == null) {
110 112 showToast("微信订单创建失败");
111 113 return;
112 114 }
... ... @@ -122,18 +124,18 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
122 124 sign: wxPayOrderInfo['sign'].toString(),
123 125 ));
124 126 } else {
125   - final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo);
126   - Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}");
127   - final String? aliPayInfo = aliPayOrderInfo.getOrNull("token");
  127 + final Map<String, dynamic>? aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo);
  128 + debugPrint("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo?.runtimeType}");
  129 + final String? aliPayInfo = aliPayOrderInfo?.getOrNull("token");
128 130 if (aliPayInfo == null) {
129 131 showToast("支付宝订单创建失败");
130 132 return;
131 133 }
132   - Log.d("aliPayInfo=$aliPayInfo");
  134 + debugPrint("aliPayInfo=$aliPayInfo");
133 135 ///打印aliPayOrderInfo的type
134 136 Tobias tobias = Tobias();
135 137 final Map aliPayResult = await tobias.pay(aliPayInfo);
136   - Log.d("aliPayResult=$aliPayResult");
  138 + debugPrint("aliPayResult=$aliPayResult");
137 139 // 判断resultStatus 为9000则代表支付成功
138 140 if (aliPayResult.getOrNull("resultStatus") == "9000") {
139 141 showToast("支付成功");
... ...
lib/pages/shopping/event.dart
... ... @@ -19,4 +19,8 @@ class DoPayEvent extends ShoppingEvent {
19 19 final PaymentChannel paymentChannel;
20 20  
21 21 DoPayEvent(this.productEntity, this.paymentChannel);
22   -}
23 22 \ No newline at end of file
  23 +}
  24 +
  25 +// 微信由于是异步回调方式通知支付状态,在异步回调里emitter(PaySuccessState())时报错
  26 +// !_isCompleted emit was called after an event handler completed normally
  27 +class WxPaySuccessEvent extends ShoppingEvent {}
24 28 \ No newline at end of file
... ...
lib/pages/shopping/view.dart
1   -import 'package:cached_network_image/cached_network_image.dart';
2 1 import 'package:flutter/material.dart';
3 2 import 'package:flutter_bloc/flutter_bloc.dart';
4 3 import 'package:flutter_screenutil/flutter_screenutil.dart';
... ... @@ -8,6 +7,7 @@ import &#39;package:wow_english/models/product_entity.dart&#39;;
8 7 import '../../common/core/assets_const.dart';
9 8 import '../../common/widgets/we_app_bar.dart';
10 9 import '../../utils/image_util.dart';
  10 +import '../user/bloc/user_bloc.dart';
11 11 import 'bloc.dart';
12 12 import 'event.dart';
13 13 import 'state.dart';
... ... @@ -65,9 +65,11 @@ class _ShoppingView extends StatelessWidget {
65 65 @override
66 66 Widget build(BuildContext context) {
67 67 final bloc = BlocProvider.of<ShoppingBloc>(context);
  68 + final userBloc = BlocProvider.of<UserBloc>(context);
68 69 return BlocListener<ShoppingBloc, ShoppingState>(
69 70 listener: (context, state) {
70 71 if (state is PaySuccessState) {
  72 + userBloc.add(PayStateChangeEvent());
71 73 Navigator.pop(context);
72 74 }
73 75 },
... ...
lib/pages/user/bloc/user_bloc.dart
... ... @@ -12,12 +12,16 @@ part &#39;user_state.dart&#39;;
12 12 class UserBloc extends Bloc<UserEvent, UserState> {
13 13 final TextEditingController modifyTextController = TextEditingController();
14 14 int tempGender = 0;
  15 + UserEntity? _userEntityForPay;
  16 +
  17 + UserEntity? get userEntityForPay => _userEntityForPay;
15 18  
16 19 UserBloc() : super(UserInitial()) {
17 20 on<UserLogout>(_logout);
18 21 on<UserDelete>(_deleteAccount);
19 22 on<UserUpdate>(_updateUser);
20 23 on<UserUIUpdate>(_updateUIUser);
  24 + on<PayStateChangeEvent>(_patStateChanged);
21 25 }
22 26  
23 27 void _logout(UserLogout event, Emitter<UserState> emitter) async {
... ... @@ -78,4 +82,17 @@ class UserBloc extends Bloc&lt;UserEvent, UserState&gt; {
78 82 Log.e('修改个人信息失败: $e');
79 83 }
80 84 }
  85 +
  86 + //支付状态变化
  87 + void _patStateChanged(PayStateChangeEvent event, Emitter<UserState> emitter) async {
  88 + // 由于userInfo接口不会返回token,所以这里需要再次保存一下token
  89 + final token = UserUtil.getUser()?.token;
  90 + _userEntityForPay = await UserDao.getUserInfo();
  91 + if (_userEntityForPay == null) {
  92 + return;
  93 + }
  94 + _userEntityForPay?.token = token;
  95 + UserUtil.saveUser(_userEntityForPay);
  96 + emitter(UserPayStateChangedState());
  97 + }
81 98 }
... ...
lib/pages/user/bloc/user_event.dart
... ... @@ -22,3 +22,5 @@ class UserUIUpdate extends UserEvent {
22 22  
23 23 UserUIUpdate(this.type);
24 24 }
  25 +
  26 +class PayStateChangeEvent extends UserEvent {}
25 27 \ No newline at end of file
... ...
lib/pages/user/bloc/user_state.dart
... ... @@ -5,3 +5,5 @@ sealed class UserState {}
5 5 class UserInitial extends UserState {}
6 6  
7 7 class UserInfoUpdated extends UserState {}
  8 +
  9 +class UserPayStateChangedState extends UserState {}
... ...
pubspec.yaml
... ... @@ -105,6 +105,8 @@ dependencies:
105 105 flutter_oss_aliyun: ^6.2.7
106 106 # App信息 https://pub.dev/packages/package_info_plus
107 107 package_info_plus: ^4.2.0
  108 + # 应用内更新 https://pub-web.flutter-io.cn/packages/flutter_app_update
  109 + flutter_app_update: ^3.0.4
108 110  
109 111 dev_dependencies:
110 112 build_runner: ^2.4.4
... ...