Commit cde7505edb1e27ff678d7900b19baf5fd5718324

Authored by 吴启风
1 parent 795fb23f

feat:应用内升级

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 + // coco2d游戏
96 97 implementation 'io.keyss.android.library:bjgame:1.0.3'
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
... ...
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/common/core/app_config_helper.dart
... ... @@ -10,14 +10,17 @@ import '../request/dao/system_dao.dart';
10 10  
11 11 class AppConfigHelper {
12 12  
13   - static AppConfigEntityEntity? configEntityEntity;
  13 + static AppConfigEntity? configEntityEntity;
14 14  
15   - static String versionCode = '';
  15 + static String _versionCode = '';
16 16  
17   - /// 获取用户信息
18   - static Future<Void?> getAppConfig() async {
  17 + // 获取用户信息
  18 + static Future<AppConfigEntity?> getAppConfig() async {
  19 + if (configEntityEntity != null) {
  20 + return configEntityEntity;
  21 + }
19 22 configEntityEntity = await SystemDao.getAppConfig();
20   - return null;
  23 + return configEntityEntity;
21 24 }
22 25  
23 26 // 是否需要隐藏...
... ... @@ -27,15 +30,15 @@ class AppConfigHelper {
27 30  
28 31 // 获取app版本号
29 32 static Future<String> getAppVersion() async {
30   - if (versionCode.isNotEmpty) {
31   - return versionCode;
  33 + if (_versionCode.isNotEmpty) {
  34 + return _versionCode;
32 35 }
33 36 PackageInfo packageInfo = await PackageInfo.fromPlatform();
34   - String version = packageInfo.version; // 版本号
35   - String buildNumber = packageInfo.buildNumber; // 构建号
36   - versionCode = version;
  37 + String versionName = packageInfo.version; // 版本号
  38 + String versionCode = packageInfo.buildNumber; // 构建号
  39 + _versionCode = versionCode;
37 40  
38   - debugPrint('versionCode=$versionCode platForm=${Platform.operatingSystem}');
  41 + debugPrint('versionName=$versionName versionCode=$versionCode platForm=${Platform.operatingSystem}');
39 42 return versionCode;
40 43 }
41 44 }
... ...
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/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/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/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';
... ... @@ -8,6 +14,7 @@ import &#39;package:wow_english/pages/user/bloc/user_bloc.dart&#39;;
8 14  
9 15 import '../../common/core/user_util.dart';
10 16 import '../../common/dialogs/show_dialog.dart';
  17 +import '../../models/app_config_entity.dart';
11 18 import 'bloc.dart';
12 19 import 'event.dart';
13 20 import 'package:flutter_screenutil/flutter_screenutil.dart';
... ... @@ -19,7 +26,9 @@ class ModuleSelectPage extends StatelessWidget {
19 26 @override
20 27 Widget build(BuildContext context) {
21 28 return BlocProvider(
22   - create: (BuildContext context) => ModuleSelectBloc()..add(InitEvent()),
  29 + create: (BuildContext context) =>
  30 + ModuleSelectBloc()
  31 + ..add(InitEvent()),
23 32 child: Builder(builder: (context) => _HomePageView()),
24 33 );
25 34 }
... ... @@ -33,79 +42,33 @@ class _HomePageView extends StatelessWidget {
33 42 debugPrint('WQF ModuleSelectPage BlocListener state: $state');
34 43 }),
35 44 BlocListener<ModuleSelectBloc, ModuleSelectState>(
36   - listener: (context, state) {},
  45 + listener: (context, state) {
  46 + if (state is UpdateDialogState) {
  47 + _showUpdateDialog(context, state.forceUpdate, state.appConfigEntity);
  48 + }
  49 + },
37 50 ),
38 51 ], child: _homeView());
39 52 }
40 53  
41   - Widget _homeView() => BlocBuilder<ModuleSelectBloc, ModuleSelectState>(
  54 + Widget _homeView() =>
  55 + BlocBuilder<ModuleSelectBloc, ModuleSelectState>(
42 56 builder: (context, state) {
43   - return Scaffold(
44   - body: Container(
45   - color: Colors.white,
46   - child: Column(
47   - children: [
48   - const BaseHomeHeaderWidget(),
49   - Expanded(
50   - child: Center(
51   - child: Row(
52   - children: [
53   - Expanded(
54   - child: GestureDetector(
55   - onTap: () {
56   - if (UserUtil.isLogined()) {
57   - pushNamed(AppRouteName.home);
58   - } else {
59   - pushNamed(AppRouteName.login);
60   - }
61   - },
62   - child: Column(
63   - mainAxisAlignment: MainAxisAlignment.center,
64   - children: [
65   - Stack(
66   - alignment: AlignmentDirectional.center,
67   - children: [
68   - Image.asset('bg_frame_module'.assetPng,
69   - width: 162.5.w, height: 203.5.h),
70   - Center(
71   - child: Image.asset(
72   - 'pic_module_study'.assetPng,
73   - width: 140.5.w,
74   - height: 172.h),
75   - )
76   - ]),
77   - 10.verticalSpace,
78   - Image.asset('label_module_study'.assetPng,
79   - width: 124.w, height: 34.h),
80   - ],
81   - ),
82   - ),
83   - ),
84   - Expanded(
85   - child: BlocBuilder<UserBloc, UserState>(
86   - builder: (context, userState) {
87   - final userBloc = BlocProvider.of<UserBloc>(context);
88   - debugPrint(
89   - 'WQF ModuleSelectPage BlocBuilder state: $userState');
90   - return GestureDetector(
  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(
91 69 onTap: () {
92   - //如果没登录先登录
93 70 if (UserUtil.isLogined()) {
94   - if (AppConfigHelper.shouldHidePay()) {
95   - pushNamed(AppRouteName.games);
96   - } else {
97   - if (UserUtil.hasGamePermission()) {
98   - pushNamed(AppRouteName.games);
99   - } else {
100   - showTwoActionDialog('提示', '忽略', '去续费',
101   - '您的课程已到期,请快快续费继续学习吧!', leftTap: () {
102   - popPage();
103   - }, rightTap: () {
104   - popPage();
105   - pushNamed(AppRouteName.shop);
106   - });
107   - }
108   - }
  71 + pushNamed(AppRouteName.home);
109 72 } else {
110 73 pushNamed(AppRouteName.login);
111 74 }
... ... @@ -118,27 +81,157 @@ class _HomePageView extends StatelessWidget {
118 81 children: [
119 82 Image.asset(
120 83 'bg_frame_module'.assetPng,
121   - width: 162.5.w,
122   - height: 203.5.h),
123   - Image.asset(
124   - 'pic_module_game'.assetPng,
125   - width: 140.5.w,
126   - height: 172.h)
  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 + )
127 91 ]),
128 92 10.verticalSpace,
129   - Image.asset('label_module_game'.assetPng,
  93 + Image.asset('label_module_study'.assetPng,
130 94 width: 124.w, height: 34.h),
131 95 ],
132   - ));
133   - }),
  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 + ],
134 159 ),
135   - ],
136   - ),
137   - ),
138   - )
139   - ],
140   - ),
  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 + ],
141 223 ),
142 224 );
143   - });
  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 + }
144 237 }
... ...
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
... ...