Files
flutterApp/lib/core/update/update_cubit.dart
Songzex 71aea48d9f 集成接口 开始执行任务的领域层和数据层和UI层的开发啊(待测试)
集成功能 暂停 取消功能 恢复功能的接口的领域层和数据层的开发 下一步待集成到页面上
优化功能,优化了接口异常和未知异常对页面渲染的影响对用户的体验的不好情况。具体通过弹窗友好提示!
优化更新了tcp指示灯点击出现机器状态中添加选中设备的编号 方便用户使用的明白明了!。
优化更新关闭了tcp重连操作内链条中的获取用户设备和切换设备操作项。
调整了获取无人机状态信息的更新频率 为14秒一次
2026-06-16 17:34:00 +08:00

219 lines
7.5 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> fullApkUpdateWithBrowser(String apkUrl) async {
try {
_logger.i('🌐 整包更新:打开浏览器下载');
final uri = Uri.parse(apkUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
_logger.i('✅ 已打开浏览器下载');
// 🔥 整包更新后,立即清除补丁版本记录(避免死循环)
await _versionService.clearPatchVersionInfo();
_logger.i('🗑️ 已清除补丁版本记录');
emit(UpdateSuccess());
} else {
_logger.e('❌ 无法打开浏览器');
emit(const UpdateFailure('无法打开浏览器'));
}
} catch (e) {
_logger.e('❌ 打开浏览器异常: $e');
emit(UpdateFailure('打开浏览器失败: $e'));
}
}
/// 取消更新
void cancelUpdate() {
_logger.i('❌ 用户取消更新');
emit(UpdateInitial());
}
}