diff --git a/android/app/build.gradle b/android/app/build.gradle index 8dd901f..92f2904 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,10 +88,11 @@ dependencies { // sing sound implementation 'com.singsound.library:evaluating:2.1.9' - implementation "com.google.code.gson:gson:2.9.0" + implementation "com.google.code.gson:gson:2.10" // 基础依赖包,必须要依赖 implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2' // kotlin扩展(可选) implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2' - implementation 'io.keyss.android.library:bjgame:1.0.3' + // coco2d游戏 + implementation 'io.keyss.android.library:steve_game:1.0.0' } diff --git a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt index 7838dad..a207dd8 100644 --- a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt +++ b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt @@ -1,11 +1,7 @@ package com.kouyuxingqiu.wow_english.methodChannels import android.content.Intent -import android.content.Intent.getIntent import android.util.Log -import androidx.core.content.ContextCompat.startActivity -import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper -import com.kouyuxingqiu.wow_english.util.GlobalHandler import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -45,7 +41,7 @@ class GameMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEngine) val gameId = call.argument("gameId") activity.startActivity( Intent(activity, AppActivity::class.java).apply { - putExtra("game", gameId) + putExtra("gameId", gameId) }) result.success(true) } else { diff --git a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt index 78d945f..964598d 100644 --- a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt +++ b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt @@ -8,7 +8,6 @@ import com.kouyuxingqiu.wow_english.util.GlobalHandler import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel -import org.json.JSONObject import java.lang.ref.WeakReference /** diff --git a/lib/app/splash_page.dart b/lib/app/splash_page.dart index 0aa8159..50d73ff 100644 --- a/lib/app/splash_page.dart +++ b/lib/app/splash_page.dart @@ -16,6 +16,9 @@ import 'package:wow_english/route/route.dart'; import 'package:wow_english/utils/log_util.dart'; import 'package:wow_english/utils/sp_util.dart'; +import '../common/core/app_consts.dart'; +import '../common/widgets/webview_dialog.dart'; + class SplashPage extends StatelessWidget { const SplashPage({super.key}); @@ -72,7 +75,33 @@ class _TransitionViewState extends State { } else { pushNamedAndRemoveUntil(AppRouteName.login, (route) => false); }*/ - pushNamedAndRemoveUntil(AppRouteName.moduleSelect, (route) => false); + bool isAggreementAccepted = AppConfigHelper.getAgreementAccepted(); + if (isAggreementAccepted) { + pushNamedAndRemoveUntil(AppRouteName.moduleSelect, (route) => false); + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () => Future.value(false), + child: WebviewDialog( + title: "服务条款及隐私政策", + webUrl: AppConsts.userPrivacyPolicyUrl, + leftTap: () { + AppConfigHelper.saveAgreementAccepted(true); + pushNamedAndRemoveUntil( + AppRouteName.moduleSelect, (route) => false); + }, + rightTap: () { + // 退出应用 + SystemNavigator.pop(); + }, + ), + ); + }, + ); + } }); } diff --git a/lib/common/core/app_config_helper.dart b/lib/common/core/app_config_helper.dart index 4933c89..18e0ded 100644 --- a/lib/common/core/app_config_helper.dart +++ b/lib/common/core/app_config_helper.dart @@ -3,21 +3,26 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:wow_english/common/core/sp_const.dart'; import 'package:wow_english/common/core/user_util.dart'; import '../../models/app_config_entity.dart'; +import '../../utils/sp_util.dart'; import '../request/dao/system_dao.dart'; class AppConfigHelper { - static AppConfigEntityEntity? configEntityEntity; + static AppConfigEntity? configEntityEntity; - static String versionCode = ''; + static String _versionCode = ''; - /// 获取用户信息 - static Future getAppConfig() async { + // 获取用户信息 + static Future getAppConfig() async { + if (configEntityEntity != null) { + return configEntityEntity; + } configEntityEntity = await SystemDao.getAppConfig(); - return null; + return configEntityEntity; } // 是否需要隐藏... @@ -27,15 +32,27 @@ class AppConfigHelper { // 获取app版本号 static Future getAppVersion() async { - if (versionCode.isNotEmpty) { - return versionCode; + if (_versionCode.isNotEmpty) { + return _versionCode; } PackageInfo packageInfo = await PackageInfo.fromPlatform(); - String version = packageInfo.version; // 版本号 - String buildNumber = packageInfo.buildNumber; // 构建号 - versionCode = version; + String versionName = packageInfo.version; // 版本号 + String versionCode = packageInfo.buildNumber; // 构建号 + _versionCode = versionCode; - debugPrint('versionCode=$versionCode platForm=${Platform.operatingSystem}'); + debugPrint('versionName=$versionName versionCode=$versionCode platForm=${Platform.operatingSystem}'); return versionCode; } + + static void saveAgreementAccepted(bool accepted) { + SpUtil.getInstance().setData(SpConst.prefsKeyAgreementAccepted, accepted); + } + + static bool getAgreementAccepted() { + return SpUtil.getInstance().get(SpConst.prefsKeyAgreementAccepted) ?? false; + } + + static void _clearUserData() { + SpUtil.getInstance().remove(SpConst.prefsKeyAgreementAccepted); + } } diff --git a/lib/common/core/sp_const.dart b/lib/common/core/sp_const.dart index 608f51f..547e52a 100644 --- a/lib/common/core/sp_const.dart +++ b/lib/common/core/sp_const.dart @@ -1,3 +1,5 @@ class SpConst { static const String prefsKeyUserInfo = "key_user_info"; + + static const String prefsKeyAgreementAccepted = "privacy_agreement_accepted"; } diff --git a/lib/common/core/user_util.dart b/lib/common/core/user_util.dart index 3875d57..5c3515a 100644 --- a/lib/common/core/user_util.dart +++ b/lib/common/core/user_util.dart @@ -65,6 +65,7 @@ class UserUtil { // 是否有游戏权限 static bool hasGamePermission() { + debugPrint('hasGamePermission: ${_userEntity}'); return _userEntity?.valid ?? false; } diff --git a/lib/common/request/dao/shop_dao.dart b/lib/common/request/dao/shop_dao.dart index ef8cd26..5558f69 100644 --- a/lib/common/request/dao/shop_dao.dart +++ b/lib/common/request/dao/shop_dao.dart @@ -8,20 +8,20 @@ class ShopDao { } ///创建订单 - static Future createOrder(ProductEntity productEntity) async { + static Future?> createOrder(ProductEntity productEntity) async { return await requestClient - .post>(Apis.createOrder, data: {'courseComboId': productEntity.id}); + .post?>(Apis.createOrder, data: {'courseComboId': productEntity.id}); } ///获取alipay支付订单信息 - static Future getAliPayToken(String orderNo) async { + static Future?> getAliPayToken(String orderNo) async { return await requestClient - .post>(Apis.getAliPayToken, data: {'orderNo': orderNo}); + .post?>(Apis.getAliPayToken, data: {'orderNo': orderNo}); } ///获取weixin支付订单信息 - static Future getWxPayToken(String orderNo) async { + static Future?> getWxPayToken(String orderNo) async { return await requestClient - .post>(Apis.getWxPayToken, data: {'orderNo': orderNo}); + .post?>(Apis.getWxPayToken, data: {'orderNo': orderNo}); } } diff --git a/lib/common/request/dao/system_dao.dart b/lib/common/request/dao/system_dao.dart index 20155ac..21cdb54 100644 --- a/lib/common/request/dao/system_dao.dart +++ b/lib/common/request/dao/system_dao.dart @@ -4,7 +4,7 @@ import '../request_client.dart'; class SystemDao { /// 获取配置信息 - static Future getAppConfig() async { + static Future getAppConfig() async { return await requestClient.get(Apis.appConfig); } } diff --git a/lib/common/widgets/webview_dialog.dart b/lib/common/widgets/webview_dialog.dart new file mode 100644 index 0000000..b05a4cf --- /dev/null +++ b/lib/common/widgets/webview_dialog.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebviewDialog extends StatelessWidget { + final String title; + final String webUrl; + final VoidCallback leftTap; + final VoidCallback rightTap; + + const WebviewDialog( + {super.key, + required this.title, + required this.webUrl, + required this.leftTap, + required this.rightTap}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Center( + child: Text(title), + ), + content: SizedBox( + width: MediaQuery.of(context).size.height - 100, + height: MediaQuery.of(context).size.width - 100, + child: FutureBuilder( + // 异步方法 + future: buildWebViewController(webUrl), + builder: (context, snapshot) { + // 等待状态显示的widget + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + // 错误时显示的widget + } else if (snapshot.hasError) { + return const Text('Error'); + } else { + return snapshot.data ?? const Text('No data'); + } + })), + actions: [ + TextButton( + child: + const Text('同意并继续', style: TextStyle(color: Color(0xFFFBB621))), + onPressed: () { + // 处理接受按钮的点击事件 + leftTap(); // 关闭对话框 + }, + ), + TextButton( + child: const Text('不同意,退出应用'), + onPressed: () { + // 处理拒绝按钮的点击事件 + rightTap(); // 关闭对话框 + }, + ), + ], + ); + } + + Future buildWebViewController(String url) async { + Widget res; + try { + WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) { + EasyLoading.show(); + }, + onPageFinished: (String url) { + EasyLoading.dismiss(); + }, + onWebResourceError: (WebResourceError error) { + EasyLoading.showError(error.description); + }, + onNavigationRequest: (NavigationRequest request) { + return NavigationDecision.navigate; + }, + ), + ); + await controller.loadRequest(Uri.parse(url)); + res = WebViewWidget(controller: controller); + } catch (error) { + res = Text("加载失败:${error.toString()}"); + debugPrint("WebViewController加载失败:${error.toString()}"); + } + return res; + } +} diff --git a/lib/generated/json/app_config_entity.g.dart b/lib/generated/json/app_config_entity.g.dart index e917a9e..c8e4f42 100644 --- a/lib/generated/json/app_config_entity.g.dart +++ b/lib/generated/json/app_config_entity.g.dart @@ -1,9 +1,9 @@ import 'package:wow_english/generated/json/base/json_convert_content.dart'; import 'package:wow_english/models/app_config_entity.dart'; -AppConfigEntityEntity $AppConfigEntityEntityFromJson( +AppConfigEntity $AppConfigEntityEntityFromJson( Map json) { - final AppConfigEntityEntity appConfigEntityEntity = AppConfigEntityEntity(); + final AppConfigEntity appConfigEntityEntity = AppConfigEntity(); final bool? androidForceUpdate = jsonConvert.convert( json['androidForceUpdate']); if (androidForceUpdate != null) { @@ -50,7 +50,7 @@ AppConfigEntityEntity $AppConfigEntityEntityFromJson( } Map $AppConfigEntityEntityToJson( - AppConfigEntityEntity entity) { + AppConfigEntity entity) { final Map data = {}; data['androidForceUpdate'] = entity.androidForceUpdate; data['androidRecommendUpdate'] = entity.androidRecommendUpdate; @@ -64,8 +64,8 @@ Map $AppConfigEntityEntityToJson( return data; } -extension AppConfigEntityEntityExtension on AppConfigEntityEntity { - AppConfigEntityEntity copyWith({ +extension AppConfigEntityEntityExtension on AppConfigEntity { + AppConfigEntity copyWith({ bool? androidForceUpdate, bool? androidRecommendUpdate, String? androidUpdatePackageUrl, @@ -76,7 +76,7 @@ extension AppConfigEntityEntityExtension on AppConfigEntityEntity { String? noticeBeforePurchaseUrl, String? safe, }) { - return AppConfigEntityEntity() + return AppConfigEntity() ..androidForceUpdate = androidForceUpdate ?? this.androidForceUpdate ..androidRecommendUpdate = androidRecommendUpdate ?? this.androidRecommendUpdate diff --git a/lib/generated/json/base/json_convert_content.dart b/lib/generated/json/base/json_convert_content.dart index 15812c8..f414ee0 100644 --- a/lib/generated/json/base/json_convert_content.dart +++ b/lib/generated/json/base/json_convert_content.dart @@ -155,9 +155,9 @@ class JsonConvert { Map e) => AliyunOssUploadStsCallbackParam.fromJson(e)).toList() as M; } - if ([] is M) { - return data.map((Map e) => - AppConfigEntityEntity.fromJson(e)).toList() as M; + if ([] is M) { + return data.map((Map e) => + AppConfigEntity.fromJson(e)).toList() as M; } if ([] is M) { return data.map((Map e) => @@ -236,7 +236,7 @@ class JsonConvertClassCollection { (AliyunOssUploadStsEntity).toString(): AliyunOssUploadStsEntity.fromJson, (AliyunOssUploadStsCallbackParam) .toString(): AliyunOssUploadStsCallbackParam.fromJson, - (AppConfigEntityEntity).toString(): AppConfigEntityEntity.fromJson, + (AppConfigEntity).toString(): AppConfigEntity.fromJson, (CourseEntity).toString(): CourseEntity.fromJson, (CourseCourseLessons).toString(): CourseCourseLessons.fromJson, (CourseModuleEntity).toString(): CourseModuleEntity.fromJson, diff --git a/lib/models/app_config_entity.dart b/lib/models/app_config_entity.dart index 28873cc..4f008e3 100644 --- a/lib/models/app_config_entity.dart +++ b/lib/models/app_config_entity.dart @@ -5,7 +5,7 @@ import '../generated/json/app_config_entity.g.dart'; @JsonSerializable() -class AppConfigEntityEntity { +class AppConfigEntity { // 安卓是否强制更新 bool? androidForceUpdate; @@ -17,14 +17,17 @@ class AppConfigEntityEntity { String? androidUpdatePackageUrl; // 安卓当前版本号 - int? androidVersion; + late int androidVersion; bool? iosForceUpdate; bool? iosRecommendUpdate; // ios版本 - int? iosVersion; + late int iosVersion; + + // 更新说明 + String? updatePackageDescription; // 购前须知图片 String? noticeBeforePurchaseUrl; @@ -33,9 +36,9 @@ class AppConfigEntityEntity { String? safe; - AppConfigEntityEntity(); + AppConfigEntity(); - factory AppConfigEntityEntity.fromJson(Map json) => $AppConfigEntityEntityFromJson(json); + factory AppConfigEntity.fromJson(Map json) => $AppConfigEntityEntityFromJson(json); Map toJson() => $AppConfigEntityEntityToJson(this); diff --git a/lib/models/user_entity.dart b/lib/models/user_entity.dart index 44a68c1..5ed0e24 100644 --- a/lib/models/user_entity.dart +++ b/lib/models/user_entity.dart @@ -63,6 +63,11 @@ class UserEntity { : '女'; } + // 是否有游戏权限 + bool hasGamePermission() { + return valid ?? false; + } + UserEntity copyWith({ int? id, String? name, diff --git a/lib/pages/moduleSelect/bloc.dart b/lib/pages/moduleSelect/bloc.dart index e4bd5f8..8d967d9 100644 --- a/lib/pages/moduleSelect/bloc.dart +++ b/lib/pages/moduleSelect/bloc.dart @@ -1,16 +1,42 @@ import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:wow_english/models/app_config_entity.dart'; +import '../../common/core/app_config_helper.dart'; import 'event.dart'; import 'state.dart'; - class ModuleSelectBloc extends Bloc { - ModuleSelectBloc() : super(ModuleSelectState().init()) { on(_init); } void _init(InitEvent event, Emitter emit) async { - emit(state.clone()); + await _checkUpdate(emit); + debugPrint('WQF ModuleSelectBloc _init'); + } + + Future _checkUpdate(Emitter emit) async { + int localVersion = int.parse(await AppConfigHelper.getAppVersion()); + AppConfigEntity? appConfigEntity = await AppConfigHelper.getAppConfig(); + if (appConfigEntity == null) { + return; + } + debugPrint('WQF _checkUpdate'); + if (defaultTargetPlatform == TargetPlatform.iOS) { + if (localVersion < appConfigEntity.iosVersion && + appConfigEntity.iosRecommendUpdate == true) { + emit(UpdateDialogState( + appConfigEntity.iosForceUpdate ?? false, appConfigEntity)); + } + } else { + if (localVersion < appConfigEntity.androidVersion && + appConfigEntity.androidRecommendUpdate == true) { + emit(UpdateDialogState( + appConfigEntity.androidForceUpdate ?? false, appConfigEntity, + )); + } + } } } diff --git a/lib/pages/moduleSelect/state.dart b/lib/pages/moduleSelect/state.dart index 8a51120..34cfc2d 100644 --- a/lib/pages/moduleSelect/state.dart +++ b/lib/pages/moduleSelect/state.dart @@ -1,3 +1,5 @@ +import '../../models/app_config_entity.dart'; + class ModuleSelectState { ModuleSelectState init() { return ModuleSelectState(); @@ -7,3 +9,12 @@ class ModuleSelectState { return ModuleSelectState(); } } + +class UpdateDialogState extends ModuleSelectState { + + final AppConfigEntity appConfigEntity; + + final bool forceUpdate; + + UpdateDialogState(this.forceUpdate, this.appConfigEntity); +} diff --git a/lib/pages/moduleSelect/view.dart b/lib/pages/moduleSelect/view.dart index c8b67ac..bff9243 100644 --- a/lib/pages/moduleSelect/view.dart +++ b/lib/pages/moduleSelect/view.dart @@ -1,12 +1,20 @@ + +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'; +import 'package:url_launcher/url_launcher.dart'; import 'package:wow_english/common/core/app_config_helper.dart'; import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/pages/moduleSelect/state.dart'; import 'package:wow_english/pages/moduleSelect/widgets/BaseHomeHeaderWidget.dart'; +import 'package:wow_english/pages/user/bloc/user_bloc.dart'; import '../../common/core/user_util.dart'; import '../../common/dialogs/show_dialog.dart'; +import '../../models/app_config_entity.dart'; import 'bloc.dart'; import 'event.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -18,7 +26,9 @@ class ModuleSelectPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => ModuleSelectBloc()..add(InitEvent()), + create: (BuildContext context) => + ModuleSelectBloc() + ..add(InitEvent()), child: Builder(builder: (context) => _HomePageView()), ); } @@ -27,106 +37,201 @@ class ModuleSelectPage extends StatelessWidget { class _HomePageView extends StatelessWidget { @override Widget build(BuildContext context) { - final bloc = BlocProvider.of(context); - return BlocListener( - listener: (context, state) {}, - child: _homeView(), - ); + return MultiBlocListener(listeners: [ + BlocListener(listener: (context, state) { + debugPrint('WQF ModuleSelectPage BlocListener state: $state'); + }), + BlocListener( + listener: (context, state) { + if (state is UpdateDialogState) { + _showUpdateDialog(context, state.forceUpdate, state.appConfigEntity); + } + }, + ), + ], child: _homeView()); } - Widget _homeView() => BlocBuilder( + Widget _homeView() => + BlocBuilder( builder: (context, state) { - final bloc = BlocProvider.of(context); - return Scaffold( - body: Container( - color: Colors.white, - child: Column( - children: [ - const BaseHomeHeaderWidget(), - Expanded( - child: Center( - child: Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () { - if (UserUtil.isLogined()) { - pushNamed(AppRouteName.home); - } else { - pushNamed(AppRouteName.login); - } - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Stack( - alignment: AlignmentDirectional.center, - children: [ - Image.asset('bg_frame_module'.assetPng, - width: 162.5.w, height: 203.5.h), - Center( - child: Image.asset( - 'pic_module_study'.assetPng, - width: 140.5.w, - height: 172.h), - ) - ]), - 10.verticalSpace, - Image.asset('label_module_study'.assetPng, - width: 124.w, height: 34.h), - ], - ), - ), - ), - Expanded( - child: GestureDetector( - onTap: () { - //如果没登录先登录 - if (UserUtil.isLogined()) { - if (AppConfigHelper.shouldHidePay()) { - pushNamed(AppRouteName.games); + return Scaffold( + body: Container( + color: Colors.white, + child: Column( + children: [ + const BaseHomeHeaderWidget(), + Expanded( + child: Center( + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + if (UserUtil.isLogined()) { + pushNamed(AppRouteName.home); } else { - if (UserUtil.hasGamePermission()) { - pushNamed(AppRouteName.games); - } else { - showTwoActionDialog( - '提示', '忽略', '去续费', - '您的课程已到期,请快快续费继续学习吧!', leftTap: () { - popPage(); - }, rightTap: () { - popPage(); - pushNamed(AppRouteName.shop); - }); - } + pushNamed(AppRouteName.login); } - } else { - pushNamed(AppRouteName.login); - } - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Stack( - alignment: AlignmentDirectional.center, - children: [ - Image.asset('bg_frame_module'.assetPng, - width: 162.5.w, height: 203.5.h), - Image.asset('pic_module_game'.assetPng, - width: 140.5.w, height: 172.h) - ]), - 10.verticalSpace, - Image.asset('label_module_game'.assetPng, - width: 124.w, height: 34.h), - ], - )), + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset( + 'bg_frame_module'.assetPng, + width: 162.5.w, height: 203.5.h), + Center( + child: Image.asset( + 'pic_module_study'.assetPng, + width: 140.5.w, + height: 172.h), + ) + ]), + 10.verticalSpace, + Image.asset('label_module_study'.assetPng, + width: 124.w, height: 34.h), + ], + ), + ), + ), + Expanded( + child: BlocBuilder( + builder: (context, userState) { + debugPrint( + '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); + } + }, + child: Column( + mainAxisAlignment: MainAxisAlignment + .center, + children: [ + Stack( + alignment: AlignmentDirectional + .center, + children: [ + Image.asset( + 'bg_frame_module' + .assetPng, + width: 162.5.w, + height: 203.5.h), + Image.asset( + 'pic_module_game' + .assetPng, + width: 140.5.w, + height: 172.h) + ]), + 10.verticalSpace, + Image.asset( + 'label_module_game'.assetPng, + width: 124.w, height: 34.h), + ], + )); + }), + ), + ], ), - ], - ), - ), - ) - ], - ), + ), + ) + ], + ), + ), + ); + }); + + + ///Flutter侧处理升级对话框 + ///[forcedUpgrade] 是否强制升级 + _showUpdateDialog(BuildContext context, bool forcedUpgrade, + AppConfigEntity appConfigEntity) { + showDialog( + context: context, + // 当我们点击除开对话框内容以外的区域是否关闭对话需用用到barrierDismissible参数 . 这个参数默认值是true ,但不能为null . + barrierDismissible: !forcedUpgrade, + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () => Future.value(!forcedUpgrade), + child: AlertDialog( + title: const Text('发现新版本'), + content: Text( + appConfigEntity.updatePackageDescription ?? + '修复了一些已知问题'), + actions: [ + TextButton( + child: Text(forcedUpgrade ? '退出' : '取消'), + onPressed: () => + { + if (forcedUpgrade) { + SystemNavigator.pop() + } else + { + Navigator.of(context).pop() + } + }, + ), + TextButton( + child: const Text('升级'), + onPressed: () async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + _launchAppStore("6450870731"); + return; + } + final String? apkUrl = appConfigEntity.androidUpdatePackageUrl; + if (apkUrl == null || apkUrl.isEmpty) { + return; + } + UpdateModel model = UpdateModel( + apkUrl, + "wowenglish.apk", + "ic_launcher", + '', + ); + AzhonAppUpdate.update(model).then((value) => + debugPrint('$value')); + if (!forcedUpgrade) { + Navigator.of(context).pop(); + } + }, + ), + ], ), ); - }); + }, + ); + } + + void _launchAppStore(String appId) async { + final String url = 'https://apps.apple.com/cn/app/wow-english/id$appId'; + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); + } else { + throw 'Could not launch $url'; + } + } } diff --git a/lib/pages/shopping/bloc.dart b/lib/pages/shopping/bloc.dart index be856c5..abd04e1 100644 --- a/lib/pages/shopping/bloc.dart +++ b/lib/pages/shopping/bloc.dart @@ -36,6 +36,9 @@ class ShoppingBloc extends Bloc { on(_init); on(_changePaymentChannel); on(_startPay); + on((event, emit) { + emit(PaySuccessState()); + }); } void _init(InitEvent event, Emitter emit) async { @@ -55,7 +58,6 @@ class ShoppingBloc extends Bloc { void _startPay(DoPayEvent event, Emitter emitter) async { - Log.d("开始支付 ${event.productEntity} ${event.paymentChannel}"); //如果event.productEntity为空,中断流程并toast提示 if (event.productEntity == null) { showToast("商品信息为空"); @@ -64,29 +66,31 @@ class ShoppingBloc extends Bloc { final productEntitySafely = event.productEntity!; try { await loading(() async { - final Map orderInfo = await ShopDao.createOrder(productEntitySafely); - Log.d("orderInfo $orderInfo"); - final String? orderNo = orderInfo.getOrNull("orderNo"); + final Map? orderInfo = await ShopDao.createOrder(productEntitySafely); + final String? orderNo = orderInfo?.getOrNull("orderNo"); if (orderNo == null) { showToast("订单创建失败"); return; } - Log.d("orderNo $orderNo"); if (event.paymentChannel == PaymentChannel.wechatPay) { if (_isWxPayListenerInitialized == false) { _isWxPayListenerInitialized = true; fluwx = Fluwx(); - fluwx?.registerApi(appId: "wx365e5a79956a450a", + await fluwx?.registerApi(appId: "wx365e5a79956a450a", universalLink: "https://app-api.wowenglish.com.cn/app/"); wxPayResponseListener = (WeChatResponse response) { - Log.d("wxPayResponseListener $response"); + debugPrint("WqfPay wxPayResponseListener $response"); if (response is WeChatPaymentResponse) { if (response.errCode == 0) { - Log.d("wxPayResponseListener response=${response.errCode}"); + debugPrint("WqfPay wxPayResponseListener response=${response.errCode}"); showToast("支付成功"); // Log.d("emitter isDone=${emitter.isDone}"); + + /// 报错!_isCompleted emit was called after an event handler completed normally // emitter(PaySuccessState()); + + add(WxPaySuccessEvent()); } else { showToast("支付失败"); } @@ -103,10 +107,8 @@ class ShoppingBloc extends Bloc { return; } - final Map wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo); - Log.d("wxPayOrderInfo=$wxPayOrderInfo type=${wxPayOrderInfo.runtimeType}"); - final String? wxPayInfo = wxPayOrderInfo.getOrNull("appId"); - if (wxPayInfo == null) { + final Map? wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo); + if (wxPayOrderInfo == null) { showToast("微信订单创建失败"); return; } @@ -122,18 +124,18 @@ class ShoppingBloc extends Bloc { sign: wxPayOrderInfo['sign'].toString(), )); } else { - final Map aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); - Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); - final String? aliPayInfo = aliPayOrderInfo.getOrNull("token"); + final Map? aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); + debugPrint("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo?.runtimeType}"); + final String? aliPayInfo = aliPayOrderInfo?.getOrNull("token"); if (aliPayInfo == null) { showToast("支付宝订单创建失败"); return; } - Log.d("aliPayInfo=$aliPayInfo"); + debugPrint("aliPayInfo=$aliPayInfo"); ///打印aliPayOrderInfo的type Tobias tobias = Tobias(); final Map aliPayResult = await tobias.pay(aliPayInfo); - Log.d("aliPayResult=$aliPayResult"); + debugPrint("aliPayResult=$aliPayResult"); // 判断resultStatus 为9000则代表支付成功 if (aliPayResult.getOrNull("resultStatus") == "9000") { showToast("支付成功"); diff --git a/lib/pages/shopping/event.dart b/lib/pages/shopping/event.dart index 62b0db0..97acd56 100644 --- a/lib/pages/shopping/event.dart +++ b/lib/pages/shopping/event.dart @@ -19,4 +19,8 @@ class DoPayEvent extends ShoppingEvent { final PaymentChannel paymentChannel; DoPayEvent(this.productEntity, this.paymentChannel); -} \ No newline at end of file +} + +// 微信由于是异步回调方式通知支付状态,在异步回调里emitter(PaySuccessState())时报错 +// !_isCompleted emit was called after an event handler completed normally +class WxPaySuccessEvent extends ShoppingEvent {} \ No newline at end of file diff --git a/lib/pages/shopping/view.dart b/lib/pages/shopping/view.dart index c5757bc..1ef973b 100644 --- a/lib/pages/shopping/view.dart +++ b/lib/pages/shopping/view.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -8,6 +7,7 @@ import 'package:wow_english/models/product_entity.dart'; import '../../common/core/assets_const.dart'; import '../../common/widgets/we_app_bar.dart'; import '../../utils/image_util.dart'; +import '../user/bloc/user_bloc.dart'; import 'bloc.dart'; import 'event.dart'; import 'state.dart'; @@ -65,9 +65,11 @@ class _ShoppingView extends StatelessWidget { @override Widget build(BuildContext context) { final bloc = BlocProvider.of(context); + final userBloc = BlocProvider.of(context); return BlocListener( listener: (context, state) { if (state is PaySuccessState) { + userBloc.add(PayStateChangeEvent()); Navigator.pop(context); } }, diff --git a/lib/pages/user/bloc/user_bloc.dart b/lib/pages/user/bloc/user_bloc.dart index 8e0d3b0..5059e9d 100644 --- a/lib/pages/user/bloc/user_bloc.dart +++ b/lib/pages/user/bloc/user_bloc.dart @@ -12,12 +12,16 @@ part 'user_state.dart'; class UserBloc extends Bloc { final TextEditingController modifyTextController = TextEditingController(); int tempGender = 0; + UserEntity? _userEntityForPay; + + UserEntity? get userEntityForPay => _userEntityForPay; UserBloc() : super(UserInitial()) { on(_logout); on(_deleteAccount); on(_updateUser); on(_updateUIUser); + on(_patStateChanged); } void _logout(UserLogout event, Emitter emitter) async { @@ -78,4 +82,17 @@ class UserBloc extends Bloc { Log.e('修改个人信息失败: $e'); } } + + //支付状态变化 + void _patStateChanged(PayStateChangeEvent event, Emitter emitter) async { + // 由于userInfo接口不会返回token,所以这里需要再次保存一下token + final token = UserUtil.getUser()?.token; + _userEntityForPay = await UserDao.getUserInfo(); + if (_userEntityForPay == null) { + return; + } + _userEntityForPay?.token = token; + UserUtil.saveUser(_userEntityForPay); + emitter(UserPayStateChangedState()); + } } diff --git a/lib/pages/user/bloc/user_event.dart b/lib/pages/user/bloc/user_event.dart index 69423e1..fc03e7b 100644 --- a/lib/pages/user/bloc/user_event.dart +++ b/lib/pages/user/bloc/user_event.dart @@ -22,3 +22,5 @@ class UserUIUpdate extends UserEvent { UserUIUpdate(this.type); } + +class PayStateChangeEvent extends UserEvent {} \ No newline at end of file diff --git a/lib/pages/user/bloc/user_state.dart b/lib/pages/user/bloc/user_state.dart index 6b74425..f647ed8 100644 --- a/lib/pages/user/bloc/user_state.dart +++ b/lib/pages/user/bloc/user_state.dart @@ -5,3 +5,5 @@ sealed class UserState {} class UserInitial extends UserState {} class UserInfoUpdated extends UserState {} + +class UserPayStateChangedState extends UserState {} diff --git a/pubspec.yaml b/pubspec.yaml index 6966f1c..3150f6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -105,6 +105,8 @@ dependencies: flutter_oss_aliyun: ^6.2.7 # App信息 https://pub.dev/packages/package_info_plus package_info_plus: ^4.2.0 + # 应用内更新 https://pub-web.flutter-io.cn/packages/flutter_app_update + flutter_app_update: ^3.0.4 dev_dependencies: build_runner: ^2.4.4