Commit 0c242392 by tanghuan

H5版本检测和更新,支持强制更新和非强制的提示更新

1 parent 0d825f57
......@@ -11,19 +11,26 @@ import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/services/player_service.dart';
import 'package:appframe/services/recorder_service.dart';
import 'package:appframe/utils/zip_util.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';
class WebState extends Equatable {
final int selectedIndex;
final bool loaded;
final bool isUpgrading;
final bool suggestUpgrade;
final String title;
final int titleColor;
final int bgColor;
......@@ -56,6 +63,8 @@ class WebState extends Equatable {
const WebState({
this.selectedIndex = 0,
this.loaded = false,
this.isUpgrading = false,
this.suggestUpgrade = false,
this.title = '',
this.titleColor = 0xFFFFFFFF,
this.bgColor = 0xFF7691FA,
......@@ -80,6 +89,8 @@ class WebState extends Equatable {
WebState copyWith({
int? selectedIndex,
bool? loaded,
bool? isUpgrading,
bool? suggestUpgrade,
String? title,
int? titleColor,
int? bgColor,
......@@ -104,6 +115,8 @@ class WebState extends Equatable {
return WebState(
selectedIndex: selectedIndex ?? this.selectedIndex,
loaded: loaded ?? this.loaded,
isUpgrading: isUpgrading ?? this.isUpgrading,
suggestUpgrade: suggestUpgrade ?? this.suggestUpgrade,
title: title ?? this.title,
titleColor: titleColor ?? this.titleColor,
bgColor: bgColor ?? this.bgColor,
......@@ -130,6 +143,8 @@ class WebState extends Equatable {
List<Object?> get props => [
selectedIndex,
loaded,
isUpgrading,
suggestUpgrade,
title,
titleColor,
bgColor,
......@@ -163,6 +178,8 @@ class WebCubit extends Cubit<WebState> {
WebViewController get controller => _controller;
WebCubit(super.initialState) {
print('========================== 构造 WebCubit ==========================');
// 没有登录数据,跳转到登录页面
if (state.sessionCode == null || state.sessionCode == '') {
WidgetsBinding.instance.addPostFrameCallback((_) {
......@@ -174,6 +191,34 @@ class WebCubit extends Cubit<WebState> {
}
Future<void> _init() async {
// 获取版本信息
var versionConfig = await _getVersionConfig();
var correctVersion = versionConfig['version'] as String;
var downloadUrl = versionConfig['zip'] as String;
var urgency = versionConfig['urgency'] as int? ?? 1;
// 当前使用的H5版本
var curVersion = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
// 版本不一致则需要升级
if (curVersion != correctVersion) {
if (urgency == 1) {
// 一直等待升级完成
// 遮罩界面
emit(state.copyWith(isUpgrading: true));
await _upgrade(curVersion, downloadUrl);
// 升级完成后取消遮罩,继续初始化其它数据
emit(state.copyWith(isUpgrading: false));
} else {
// 后台下载,完成后提示用户
_upgrade(correctVersion, downloadUrl).then(
(value) {
emit(state.copyWith(suggestUpgrade: true));
},
);
}
}
// 消息处理器
_dispatcher = MessageDispatcher();
......@@ -183,17 +228,76 @@ class WebCubit extends Cubit<WebState> {
// 创建WebView控制器
await _createWebViewController();
// 加载H5页面
_loadHtml();
// 登录IM
_loginIM();
// 初始化其它一些属性
_fluwx = getIt.get<Fluwx>();
_playerService = getIt.get<PlayerService>();
_playerService?.sendResponse = _sendResponse;
_recorderService = getIt.get<RecorderService>();
}
Future<Map<String, String>> _getVersionConfig() async {
Dio dio = Dio();
try {
Response response = await dio.get(
'${Constant.configUrl}?t=${DateTime.now().millisecondsSinceEpoch}',
options: Options(responseType: ResponseType.json),
);
if (response.statusCode != 200) {
throw Exception('获取版本信息失败');
}
String version = response.data['version'] as String;
String zip = response.data['zip'] as String;
return {
'version': version,
'zip': 'http://192.168.2.177/1.0.0.zip',
// 'zip': zip,
};
} finally {
dio.close(force: true);
}
}
Future<void> _upgrade(String version, String zipUrl) async {
Dio dio = Dio();
try {
// 下载zip文件
var tempDir = await getTemporaryDirectory();
var saveFilePath = '${tempDir.path}/${Uuid().v4()}.zip';
Response response = await dio.download(zipUrl, saveFilePath);
if (response.statusCode != 200) {
throw Exception('文件下载失败');
}
// 解压zip文件
String? httpDirect;
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
httpDirect = '${direct?.path}/${Constant.h5DistDir}/$version';
} else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory();
httpDirect = '${direct.path}/${Constant.h5DistDir}/$version';
}
var result = await ZipUtil.extractZipFile(saveFilePath, httpDirect!);
if (!result) {
throw Exception('文件解压失败');
}
var sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString('h5_version', version);
} finally {
dio.close(force: true);
}
}
Future<void> _startLocalServer() async {
// 启动本地服务器
_server = await getIt.get<LocalServerService>().startLocalServer();
......@@ -395,6 +499,40 @@ class WebCubit extends Cubit<WebState> {
}
///
/// 升级提示
///
void suggestUpgrade(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('温馨提示'),
content: Text('资源已更新为最新版本,是否现在进行加载?'),
actions: <Widget>[
TextButton(
child: Text('取消'),
onPressed: () {
Navigator.of(context).pop();
emit(state.copyWith(suggestUpgrade: false));
},
),
TextButton(
child: Text('确定'),
onPressed: () {
Navigator.of(context).pop();
emit(state.copyWith(suggestUpgrade: false));
getIt.get<LocalServerService>().resetHttpDirectory();
_loadHtml();
},
),
],
);
},
);
}
///
///
///
void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) {
......
......@@ -20,6 +20,9 @@ class Constant {
static const String appVersion = '1.0.0';
static const String h5Version = '1.0.0';
/// H5版本号配置文件地址
static const String configUrl = 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json';
/// 内部 H5 dist 目录
static const String h5DistDir = 'http_dist_assets';
......
......@@ -36,7 +36,6 @@ import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/services/player_service.dart';
import 'package:appframe/services/recorder_service.dart';
import 'package:appframe/services/upgrade_service.dart';
import 'package:fluwx/fluwx.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
......@@ -184,9 +183,6 @@ Future<void> setupLocator() async {
/// local server
getIt.registerSingleton<LocalServerService>(LocalServerService());
/// upgradeService
getIt.registerSingleton<UpgradeService>(UpgradeService());
/// apiService
getIt.registerLazySingleton<ApiService>(
() => ApiService(baseUrl: 'https://dev.banxiaoer.net'),
......
import 'package:appframe/config/locator.dart';
/// 此处暂时测试
/// 正常需要在登录状态下,查询host和jwt
Future<void> registerMqtt() async {
// String mqttHost = '58.87.99.45';
// int mqttPort = 1883;
// // 获取 mac 地址
// String mqttMac = '';
// String mqttClientId = 'tanghuan_phone';
// int keepAlive = 60;
// String mqttUsername = 'user';
// String mqttPassword =
// 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NTgzOTEsInJzIjoib2sifQ.AoU0MfSKflC1VB0abi7BVr3g4MDah1uLlg01ZTFQgTxfolu28IfZ4BaGhRF9qy7yAQH2Efmdf2cs2iwKrdcHRSHzJhwTC44beX6viRhCCiCxe51AB8NVv72l2TmsNxIvACfXOhDLjKH6QE38EaKC486aS_L-QpakvDOQP_IPjq5ZvH68JwwhOwhLTgaCgOR3xde2H-NgRDK2BQ-FyDTXi1RX8hDGvKMw8pi6WiVBjR1ENTO5A7yvMioJS9qwdjs_7_5c6n5GXSjCHTtdQ7746hlId2uwP_41G5Ug3DYWiZ5aWIuvGRH6ZxKmbC32wN62ys_XkLGzhBw8wsQ-KhETvQ ';
//
// /// 初始化MQTT客户端
// var mqttService = MqttService(mqttHost, mqttPort, mqttClientId, keepAlive, mqttUsername, mqttPassword);
// await mqttService.initConn();
//
// /// 设置到getIt,用于获取使用
// getIt.registerSingleton(mqttService);
// MqttIsolateManager mqttIsolateManager = MqttIsolateManager();
// mqttIsolateManager.start();
// await mqttIsolateManager.connect('server', 'clientId');
// // 暂停3秒
// // await Future.delayed(Duration(seconds: 2));
// mqttIsolateManager.subscribe('bxe/abc');
//
// getIt.registerSingleton(mqttIsolateManager);
}
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/upgrade_service.dart';
import 'package:flutter/material.dart';
import 'app.dart';
......@@ -14,8 +13,6 @@ void main() async {
if (Constant.needIM) {
await getIt.get<ImService>().initSdk();
}
// H5版本检测和升级
getIt.get<UpgradeService>().start();
runApp(const App());
}
......@@ -44,6 +44,10 @@ class LocalServerService {
return server;
}
void resetHttpDirectory() {
_httpDirectory = null;
}
// 目录下的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
......
import 'dart:io';
import 'dart:isolate';
import 'package:appframe/config/constant.dart';
import 'package:appframe/utils/zip_util.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
class UpgradeService {
String configUrl = "https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json";
///
/// Isolate 中检测升级
///
void start() {
final token = ServicesBinding.rootIsolateToken;
Isolate.spawn(_handleUpgrade, {'token': token});
}
Future<bool> _handleUpgrade(Map<dynamic, dynamic> params) async {
final RootIsolateToken? token = params['token'];
if (token != null) {
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
}
print('开始检测升级...');
Dio dio = Dio();
try {
// 获取版本信息
var info = await _getVersionInfo(dio);
String version = info['version'] as String;
String zip = info['zip'] as String;
// 检测版本号
// 在新 Isolate 中不能使用主 Isolate 的 getIt
var sharedPreferences = await SharedPreferences.getInstance();
// 版本号相同,则不需要升级
if (version == (sharedPreferences.getString('h5_version') ?? Constant.h5Version)) {
print('版本号相同,不需要升级');
return true;
}
// 下载zip文件
var tempDir = await getTemporaryDirectory();
var saveFilePath = '${tempDir.path}/${Uuid().v4()}.zip';
await _downloadFile(dio, '$zip$version.zip', saveFilePath);
// 解压zip文件
String? httpDirect;
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
httpDirect = '${direct?.path}/${Constant.h5DistDir}/$version';
} else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory();
httpDirect = '${direct.path}/${Constant.h5DistDir}/$version';
}
var result = await ZipUtil.extractZipFile(saveFilePath, httpDirect!);
if (!result) {
print('解压失败');
return false;
}
// 设置版本标识
sharedPreferences.setString('h5_version', version);
return true;
} catch (e) {
print('升级请求失败: $e');
return false;
} finally {
dio.close(force: true);
}
}
Future<Map<String, String>> _getVersionInfo(Dio dio) async {
Response response = await dio.get('$configUrl?t=${DateTime.now().millisecondsSinceEpoch}');
if (response.statusCode != 200) {
throw Exception('获取版本信息失败');
}
String version = response.data['version'] as String;
String zip = response.data['zip'] as String;
return {
'version': version,
'zip': zip,
};
}
Future<void> _downloadFile(Dio dio, String url, String savePath) async {
Response response = await dio.download(url, savePath);
if (response.statusCode != 200) {
throw Exception('文件下载失败');
}
}
}
......@@ -56,14 +56,6 @@ class WebPage extends StatelessWidget {
automaticallyImplyLeading: false,
backgroundColor: Color(state.bgColor),
actionsIconTheme: IconThemeData(color: Colors.white),
// leading: state.beBack
// ? IconButton(
// icon: const Icon(Icons.arrow_back, color: Colors.white),
// onPressed: () {
// ctx.read<WebCubit>().handleBack();
// },
// )
// : null,
leading: state.opIcon == 'back'
? IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
......@@ -93,14 +85,6 @@ class WebPage extends StatelessWidget {
fontSize: 24,
color: Colors.white,
))),
/*ListTile(
tileColor: Color(0xFF7691FA),
title: Text('设置',
style: TextStyle(
color: Theme.of(ctx).colorScheme.onPrimary,
fontSize: 24,
)),
),*/
ListTile(
leading: const Icon(Icons.chat_bubble_outline),
title: const Text('消息测试'),
......@@ -167,12 +151,40 @@ class WebPage extends StatelessWidget {
},
),
])),
body: state.loaded
? SizedBox(
height: MediaQuery.of(ctx).size.height - 60, // 减去100像素留空
child: WebViewWidget(controller: ctx.read<WebCubit>().controller),
)
: const Center(child: CircularProgressIndicator()),
body: Stack(
children: [
state.loaded
? SizedBox(
height: MediaQuery.of(ctx).size.height - 60, // 减去100像素留空
child: WebViewWidget(controller: ctx.read<WebCubit>().controller),
)
: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('加载中...'),
],
),
),
// 添加升级遮罩层
if (state.isUpgrading)
Container(
color: Colors.black54,
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('资源更新中...', style: TextStyle(color: Colors.white)),
],
),
),
),
],
),
bottomNavigationBar: state.showBottomNavBar
? BottomNavigationBar(
type: BottomNavigationBarType.fixed,
......@@ -208,7 +220,9 @@ class WebPage extends StatelessWidget {
);
},
listener: (context, state) {
if (state.orientationCmdFlag) {
if(state.suggestUpgrade){
context.read<WebCubit>().suggestUpgrade(context);
} else if (state.orientationCmdFlag) {
context.read<WebCubit>().getOrientation(context);
} else if (state.windowInfoCmdFlag) {
context.read<WebCubit>().getWindowInfo(context);
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!