Files
flutterApp/lib/core/update/update_cubit.dart
Songzex 335c468aac 修复版本管理整包更新无法弹出的问题
完成集成新的登录接口的更换使用
完成集成用户的场站列表接口
完成开发设计选择场站项为全局属性
完成我的页面的个人信息的集成和详情页面的开发
完成设备页面的中无人机机场的列表接口对接入和个项页面的布局的优化
完成设备页面的中无人机机场的详情接口的对接和使用和页面的更新布局
2026-05-29 14:13:34 +08:00

218 lines
7.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/services.dart';
import 'package:logger/logger.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'version_check_service.dart';
import 'update_state.dart';
/// 更新 Cubit
class UpdateCubit extends Cubit<UpdateState> {
final VersionCheckService _versionService;
final Logger _logger;
UpdateCubit(this._versionService)
: _logger = Logger(),
super(UpdateInitial());
/// 检查版本更新
Future<void> checkUpdate() async {
emit(UpdateChecking());
try {
// 从 pubspec.yaml 读取版本信息
final String versionString = await rootBundle.loadString('pubspec.yaml');
final versionMatch = RegExp(r'version:\s*(\d+\.\d+\.\d+)\+(\d+)').firstMatch(versionString);
final currentVersion = versionMatch?.group(1) ?? '1.0.0';
final currentVersionCode = int.tryParse(versionMatch?.group(2) ?? '1') ?? 1;
_logger.i('📱 当前应用版本: $currentVersion ($currentVersionCode)');
// 检查更新
final versionInfo = await _versionService.checkUpdate(
currentVersion: currentVersion,
currentVersionCode: currentVersionCode,
);
if (versionInfo == null) {
_logger.i('✅ 已是最新版本');
emit(UpdateUpToDate());
return;
}
_logger.i('🔄 发现新版本: ${versionInfo.version}');
_logger.i('📦 更新类型: ${versionInfo.updateType}');
_logger.i('⚠️ 强制更新: ${versionInfo.forceUpdate}');
emit(UpdateAvailable(versionInfo));
} catch (e) {
_logger.e('❌ 检查更新失败: $e');
emit(UpdateFailure('检查更新失败: $e'));
}
}
/// 应用差量补丁
Future<void> applyPatch(String patchUrl, String version, int targetVersionCode, {String? md5}) async {
emit(const UpdateDownloading(0.0, isPatch: true));
try {
_logger.i('⬇️ 开始下载并应用补丁');
final success = await _versionService.applyPatch(
patchUrl: patchUrl,
version: version,
targetVersionCode: targetVersionCode,
md5: md5,
onProgress: (progress) {
// 🔥 实时更新下载进度
emit(UpdateDownloading(progress, isPatch: true));
},
);
if (success) {
emit(const UpdateInstalling(isPatch: true));
emit(UpdateSuccess());
} else {
emit(const UpdateFailure('补丁应用失败'));
}
} catch (e) {
_logger.e('❌ 应用补丁异常: $e');
emit(UpdateFailure('应用补丁失败: $e'));
}
}
/// 自动下载并安装 APK(App 内下载)
Future<void> downloadAndInstallApk(String apkUrl) async {
emit(const UpdateDownloading(0.0, isPatch: false));
try {
_logger.i('⬇️ 开始下载完整 APK');
final apkPath = await _versionService.downloadApk(apkUrl, (progress) {
emit(UpdateDownloading(progress, isPatch: false));
});
if (apkPath != null) {
_logger.i('✅ APK 下载完成: $apkPath');
// 🔥 关键:先清理版本号记录,并等待用户看到提示
emit(const UpdateClearingVersion());
_logger.i('🗑️ 开始清除补丁版本记录');
await _versionService.clearPatchVersionInfo();
_logger.i('✅ 补丁版本记录已清除');
// 🔥 短暂延迟,让用户看到清理完成的提示
await Future.delayed(const Duration(milliseconds: 800));
// 🔥 直接调起系统安装页面,不再等待用户点击
_logger.i('📦 自动调起系统安装页面');
await triggerInstall(apkPath);
} else {
emit(const UpdateFailure('APK 下载失败'));
}
} catch (e) {
_logger.e('❌ 下载安装 APK 异常: $e');
emit(UpdateFailure('下载安装 APK 失败: $e'));
}
}
/// 触发系统安装页面
Future<void> triggerInstall(String apkPath) async {
emit(const UpdateInstalling(isPatch: false));
try {
_logger.i('📦 开始调起系统安装页面');
_logger.i('📁 APK 路径: $apkPath');
// 🔥 验证文件是否存在
final file = File(apkPath);
if (!await file.exists()) {
_logger.e('❌ APK 文件不存在: $apkPath');
emit(UpdateFailure('APK 文件不存在,请重新下载'));
return;
}
final fileSize = await file.length();
_logger.i('📏 APK 文件大小: ${fileSize / 1024 / 1024} MB');
// 🔥 关键修复:将APK复制到外部存储,避免某些Android版本的安装限制
final externalDir = await getExternalStorageDirectory();
String finalApkPath = apkPath;
if (externalDir != null) {
final publicApkPath = '${externalDir.path}/app-update-final.apk';
_logger.i('📋 复制APK到外部存储: $publicApkPath');
try {
await file.copy(publicApkPath);
finalApkPath = publicApkPath;
_logger.i('✅ APK复制成功');
} catch (e) {
_logger.w('⚠️ 复制APK失败,使用原路径: $e');
}
}
// 打开 APK 文件,触发系统安装界面
_logger.i('🚀 调用 OpenFile.open...');
_logger.i('🚀 最终APK路径: $finalApkPath');
final result = await OpenFile.open(finalApkPath);
_logger.i('📦 安装结果: ${result.message} (type: ${result.type})');
// 如果用户取消安装,显示提示信息并保留文件路径
if (result.type != ResultType.done) {
_logger.w('⚠️ 用户取消了安装或安装失败 (type: ${result.type})');
// 🔥 如果是权限问题,提供更友好的提示
String errorMsg = '您取消了安装';
if (result.type == ResultType.noAppToOpen) {
errorMsg = '没有找到可以安装 APK 的应用,请检查系统设置';
} else if (result.type == ResultType.permissionDenied) {
errorMsg = '没有安装权限,请在系统设置中允许"安装未知应用"';
} else if (result.type == ResultType.error) {
errorMsg = '安装出错: ${result.message}';
}
emit(UpdateFailure('$errorMsg,APK 文件位于: $finalApkPath'));
} else {
// 安装成功,关闭对话框
emit(UpdateSuccess());
}
} catch (e) {
_logger.e('❌ 调起安装页面异常: $e');
emit(UpdateFailure('调起安装页面失败: $e'));
}
}
/// 在浏览器中打开下载链接(手动下载)
Future<void> openInBrowser(String apkUrl) async {
try {
_logger.i('🌐 在浏览器中打开 APK 下载链接');
final uri = Uri.parse(apkUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
_logger.i('✅ 已打开浏览器下载');
// 🔥 整包更新后,清除补丁版本记录
await _versionService.clearPatchVersionInfo();
emit(UpdateSuccess());
} else {
_logger.e('❌ 无法打开浏览器');
emit(const UpdateFailure('无法打开浏览器'));
}
} catch (e) {
_logger.e('❌ 打开浏览器异常: $e');
emit(UpdateFailure('打开浏览器失败: $e'));
}
}
/// 取消更新
void cancelUpdate() {
_logger.i('❌ 用户取消更新');
emit(UpdateInitial());
}
}