From cde7505edb1e27ff678d7900b19baf5fd5718324 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Sat, 27 Apr 2024 21:22:44 +0800 Subject: [PATCH] feat:应用内升级 --- android/app/build.gradle | 3 ++- android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/GameMethodChannel.kt | 4 ---- android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt | 1 - lib/common/core/app_config_helper.dart | 25 ++++++++++++++----------- lib/common/core/user_util.dart | 1 + lib/common/request/dao/system_dao.dart | 2 +- lib/generated/json/app_config_entity.g.dart | 12 ++++++------ lib/generated/json/base/json_convert_content.dart | 8 ++++---- lib/models/app_config_entity.dart | 13 ++++++++----- lib/pages/moduleSelect/bloc.dart | 32 +++++++++++++++++++++++++++++--- lib/pages/moduleSelect/state.dart | 11 +++++++++++ lib/pages/moduleSelect/view.dart | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------- pubspec.yaml | 2 ++ 13 files changed, 254 insertions(+), 119 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8dd901f..2f4d250 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' + // coco2d游戏 implementation 'io.keyss.android.library:bjgame:1.0.3' } 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..b11048b 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 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/common/core/app_config_helper.dart b/lib/common/core/app_config_helper.dart index 4933c89..56c64d1 100644 --- a/lib/common/core/app_config_helper.dart +++ b/lib/common/core/app_config_helper.dart @@ -10,14 +10,17 @@ 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 +30,15 @@ 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; } } 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/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/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/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 7a4a930..bff9243 100644 --- a/lib/pages/moduleSelect/view.dart +++ b/lib/pages/moduleSelect/view.dart @@ -1,5 +1,11 @@ + +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'; @@ -8,6 +14,7 @@ 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'; @@ -19,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()), ); } @@ -33,79 +42,33 @@ class _HomePageView extends StatelessWidget { debugPrint('WQF ModuleSelectPage BlocListener state: $state'); }), BlocListener( - listener: (context, state) {}, + listener: (context, state) { + if (state is UpdateDialogState) { + _showUpdateDialog(context, state.forceUpdate, state.appConfigEntity); + } + }, ), ], child: _homeView()); } - Widget _homeView() => BlocBuilder( + Widget _homeView() => + BlocBuilder( builder: (context, state) { - 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: BlocBuilder( - builder: (context, userState) { - final userBloc = BlocProvider.of(context); - debugPrint( - 'WQF ModuleSelectPage BlocBuilder state: $userState'); - return GestureDetector( + 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()) { - if (AppConfigHelper.shouldHidePay()) { - pushNamed(AppRouteName.games); - } else { - if (UserUtil.hasGamePermission()) { - pushNamed(AppRouteName.games); - } else { - showTwoActionDialog('提示', '忽略', '去续费', - '您的课程已到期,请快快续费继续学习吧!', leftTap: () { - popPage(); - }, rightTap: () { - popPage(); - pushNamed(AppRouteName.shop); - }); - } - } + pushNamed(AppRouteName.home); } else { pushNamed(AppRouteName.login); } @@ -118,27 +81,157 @@ class _HomePageView extends StatelessWidget { 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) + 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_game'.assetPng, + 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/pubspec.yaml b/pubspec.yaml index b445450..1be205e 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 -- libgit2 0.22.2