yx_app_upgrade_flutter/lib/app_upgrade_simple.dart

1973 lines
65 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:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'app_upgrade_plugin_platform_interface.dart';
import 'core/permission_helper.dart';
import 'core/upgrade_utils.dart';
import 'models/upgrade_info.dart';
import 'widgets/market_selection_dialog.dart';
/// 简化的插件接口,避免循环导入
class _SimpleAppUpgradePlugin {
static final _SimpleAppUpgradePlugin _instance = _SimpleAppUpgradePlugin._();
_SimpleAppUpgradePlugin._();
static _SimpleAppUpgradePlugin get instance => _instance;
Future<UpgradeInfo?> checkUpdate(String url, {Map<String, dynamic>? params}) {
return AppUpgradePluginPlatform.instance.checkUpdate(url, params: params);
}
Future<String?> downloadApk(String url, {Function(DownloadProgress)? onProgress}) {
return AppUpgradePluginPlatform.instance.downloadApk(url, onProgress: onProgress);
}
Future<bool> installApk(String filePath) {
return AppUpgradePluginPlatform.instance.installApk(filePath);
}
Future<bool> goToAppStore(String url) {
return AppUpgradePluginPlatform.instance.goToAppStore(url);
}
Future<Map<String, String>> getAppInfo() {
return AppUpgradePluginPlatform.instance.getAppInfo();
}
/// 检查当前应用的版本信息(用于安装状态检测)
Future<Map<String, String>> getCurrentAppInfo() async {
try {
return await AppUpgradePluginPlatform.instance.getAppInfo();
} catch (e) {
debugPrint('获取应用信息失败: $e');
return {};
}
}
/// 获取下载路径
Future<String?> getDownloadPath() {
return AppUpgradePluginPlatform.instance.getDownloadPath();
}
/// 精确检测应用是否已安装(通过包名)
Future<bool> isPackageInstalled(String packageName) async {
try {
// 通过比较当前应用的包名来检测
final appInfo = await getAppInfo();
final currentPackage = appInfo['packageName'] ?? '';
debugPrint('检查包安装状态: 当前包名=$currentPackage, 目标包名=$packageName');
// 如果是检查当前应用的安装状态,通过版本号变化来判断
if (currentPackage == packageName) {
return true; // 当前应用肯定已安装
}
return false;
} catch (e) {
debugPrint('检查包安装状态失败: $e');
return false;
}
}
/// 比较版本号是否已更新
Future<bool> isVersionUpdated(String targetVersion, int? targetBuildNumber) async {
try {
final appInfo = await getCurrentAppInfo();
final currentVersion = appInfo['version'] ?? '';
final currentBuildNumber = int.tryParse(appInfo['buildNumber'] ?? '0') ?? 0;
debugPrint('版本对比: 当前版本=$currentVersion, 目标版本=$targetVersion');
debugPrint('构建号对比: 当前构建号=$currentBuildNumber, 目标构建号=$targetBuildNumber');
// 优先比较构建号(更准确)
if (targetBuildNumber != null && targetBuildNumber > 0) {
return currentBuildNumber >= targetBuildNumber;
}
// 备用方案:比较版本号
return currentVersion == targetVersion;
} catch (e) {
debugPrint('版本对比失败: $e');
return false;
}
}
}
/// 升级配置选项
class UpgradeConfig {
/// 是否显示无更新提示
final bool showNoUpdateToast;
/// 是否自动下载
final bool autoDownload;
/// 是否自动安装
final bool autoInstall;
/// 连接超时时间(秒)
final int connectionTimeout;
/// 下载超时时间(秒)
final int downloadTimeout;
/// 安装检测超时时间(秒)
final int installTimeout;
/// 是否启用调试日志
final bool enableDebugLog;
/// 自定义Toast显示函数
final void Function(String message)? customToast;
/// 是否需要获取安装未知应用权限默认false直接安装
final bool requireInstallPermission;
const UpgradeConfig({
this.showNoUpdateToast = true,
this.autoDownload = false,
this.autoInstall = false,
this.connectionTimeout = 30,
this.downloadTimeout = 300,
this.installTimeout = 45,
this.enableDebugLog = true,
this.customToast,
this.requireInstallPermission = false, // 默认不需要权限
});
/// 快速配置:自动更新(不需要权限)
static const UpgradeConfig auto = UpgradeConfig(
autoDownload: true,
autoInstall: true,
requireInstallPermission: false,
);
/// 快速配置:静默检查
static const UpgradeConfig silent = UpgradeConfig(
showNoUpdateToast: false,
enableDebugLog: false,
requireInstallPermission: false,
);
/// 快速配置:开发模式(详细日志 + 较短超时)
static const UpgradeConfig development = UpgradeConfig(
enableDebugLog: true,
installTimeout: 30,
connectionTimeout: 10,
requireInstallPermission: false,
);
/// 快速配置:生产模式(静默 + 较长超时)
static const UpgradeConfig production = UpgradeConfig(
showNoUpdateToast: false,
enableDebugLog: false,
installTimeout: 60,
connectionTimeout: 30,
requireInstallPermission: false,
);
/// 快速配置:需要权限模式(传统方式)
static const UpgradeConfig withPermission = UpgradeConfig(
requireInstallPermission: true,
);
}
/// 简化版App升级管理器
/// 提供最简单的API一行代码即可实现App升级功能
///
/// 版本: 2.0.0
/// 更新日期: 2025-09-18
///
/// 主要特性:
/// - 🎨 现代化UI设计支持Material Design 3
/// - 🔄 智能安装状态检测,支持多种安装场景
/// - 📱 完整的生命周期管理和错误处理
/// - ⚙️ 灵活的配置系统,支持多种使用模式
/// - 🎯 富文本支持,更好的内容展示
/// - 🚀 高性能,低内存占用
/// - 🛡️ 完善的权限处理和安全检查
class AppUpgradeSimple {
static AppUpgradeSimple? _instance;
/// 获取单例实例
static AppUpgradeSimple get instance {
_instance ??= AppUpgradeSimple._();
return _instance!;
}
@visibleForTesting
static set instance(AppUpgradeSimple value) {
_instance = value;
}
@visibleForTesting
AppUpgradeSimple.private({_SimpleAppUpgradePlugin? plugin}) : _plugin = plugin ?? _SimpleAppUpgradePlugin.instance;
AppUpgradeSimple._() : _plugin = _SimpleAppUpgradePlugin.instance;
final _SimpleAppUpgradePlugin _plugin;
UpgradeConfig _config = const UpgradeConfig();
/// 配置升级参数
void configure(UpgradeConfig config) {
_config = config;
}
/// 快速配置:一键设置自动更新
void enableAutoUpdate() {
_config = UpgradeConfig.auto;
}
/// 快速配置:一键设置静默检查
void enableSilentCheck() {
_config = UpgradeConfig.silent;
}
/// 检查网络连接状态
Future<bool> checkNetworkStatus() async {
try {
final result = await InternetAddress.lookup('baidu.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (e) {
debugPrint('网络检查失败: $e');
return false;
}
}
/// 清理下载的临时文件
Future<void> clearDownloadCache() async {
try {
final downloadPath = await _plugin.getDownloadPath();
if (downloadPath != null) {
final dir = Directory(downloadPath);
if (await dir.exists()) {
final files = await dir.list().where((file) => file.path.endsWith('.apk')).toList();
for (final file in files) {
try {
await file.delete();
debugPrint('已删除缓存文件: ${file.path}');
} catch (e) {
debugPrint('删除缓存文件失败: ${file.path}, 错误: $e');
}
}
}
}
} catch (e) {
debugPrint('清理下载缓存失败: $e');
}
}
/// 一键检查更新(最简单的使用方式)
///
/// 基础用法:
/// ```dart
/// AppUpgradeSimple.instance.checkUpdate(
/// context: context,
/// url: 'https://api.example.com/check-update',
/// );
/// ```
///
/// 高级用法:
/// ```dart
/// // 配置自动更新
/// AppUpgradeSimple.instance.configure(UpgradeConfig.auto);
///
/// // 或者使用自定义配置
/// AppUpgradeSimple.instance.configure(UpgradeConfig(
/// autoDownload: true,
/// autoInstall: false,
/// installTimeout: 60,
/// customToast: (message) => ScaffoldMessenger.of(context).showSnackBar(
/// SnackBar(content: Text(message)),
/// ),
/// ));
///
/// // 检查更新
/// await AppUpgradeSimple.instance.checkUpdate(
/// context: context,
/// url: 'https://api.example.com/check-update',
/// params: {'userId': '123', 'channel': 'official'},
/// onComplete: () => print('更新检查完成'),
/// );
/// ```
///
/// [context] 需要是一个能够访问到 `Navigator` 和 `MaterialLocalizations` 的有效上下文。
/// [url] 检查更新的API接口地址
/// [params] 可选的请求参数
/// [config] 可选的配置覆盖
/// [onComplete] 更新流程完成后的回调
/// 一键检查更新(支持配置)
Future<void> checkUpdate({
required BuildContext context,
required String url,
Map<String, dynamic>? params,
bool? showNoUpdateToast,
bool? autoDownload,
bool? autoInstall,
VoidCallback? onComplete,
UpgradeConfig? config,
}) async {
// 使用传入的配置或默认配置
final effectiveConfig = config ?? _config;
final finalShowNoUpdateToast = showNoUpdateToast ?? effectiveConfig.showNoUpdateToast;
final finalAutoDownload = autoDownload ?? effectiveConfig.autoDownload;
final finalAutoInstall = autoInstall ?? effectiveConfig.autoInstall;
try {
assert(_canShowMaterialDialog(context), '请在 MaterialApp 环境内调用');
// 检查更新
final info = await _plugin.checkUpdate(url, params: params);
if (effectiveConfig.enableDebugLog) {
debugPrint('🔍 检查更新结果: $info');
}
if (info == null || !info.hasUpdate) {
if (finalShowNoUpdateToast && context.mounted) {
_showToast('已是最新版本', effectiveConfig);
}
onComplete?.call();
return;
}
await _showUpgradeDialog(
context: context,
info: info,
autoDownload: finalAutoDownload,
autoInstall: finalAutoInstall,
onComplete: onComplete,
config: effectiveConfig,
);
} catch (e) {
debugPrint('检查更新失败: $e');
final errorString = e.toString();
final String errorMessage;
if (errorString.contains('无网络连接')) {
errorMessage = '无网络连接,请检查网络设置';
} else if (errorString.contains('Failed host lookup')) {
errorMessage = '无法连接到服务器,请检查网络或稍后重试';
} else if (errorString.contains('Connection refused')) {
errorMessage = '服务器拒绝连接,请稍后重试';
} else if (errorString.contains('timeout')) {
errorMessage = '连接超时,请检查网络';
} else {
errorMessage = '检查更新失败: ${errorString.split(':').first}';
}
if (context.mounted) {
_showToast(errorMessage, effectiveConfig);
// 如果是网络问题,显示网络诊断建议
if (errorString.contains('Failed host lookup') || errorString.contains('无网络连接')) {
if (effectiveConfig.enableDebugLog) {
debugPrint('💡 建议: 请检查网络连接或尝试使用网络诊断功能');
}
}
}
onComplete?.call();
}
}
/// 静默检查更新(不显示无更新提示)
Future<UpgradeInfo?> checkUpdateSilent({
required String url,
Map<String, dynamic>? params,
}) async {
try {
return await _plugin.checkUpdate(url, params: params);
} catch (e) {
if (_config.enableDebugLog) {
debugPrint('静默检查更新失败: $e');
}
return null;
}
}
/// 预下载APK不显示UI后台下载
Future<String?> preDownloadApk({
required String url,
Function(DownloadProgress)? onProgress,
}) async {
try {
return await _plugin.downloadApk(url, onProgress: onProgress);
} catch (e) {
if (_config.enableDebugLog) {
debugPrint('预下载APK失败: $e');
}
return null;
}
}
/// 检查是否有已下载的APK文件
Future<String?> findDownloadedApk(String version) async {
try {
final downloadPath = await _plugin.getDownloadPath();
if (downloadPath != null) {
final dir = Directory(downloadPath);
if (await dir.exists()) {
final files = await dir.list().toList();
for (final file in files) {
if (file.path.contains(version) && file.path.endsWith('.apk')) {
final fileEntity = File(file.path);
if (await fileEntity.exists()) {
return file.path;
}
}
}
}
}
return null;
} catch (e) {
if (_config.enableDebugLog) {
debugPrint('查找已下载APK失败: $e');
}
return null;
}
}
/// 获取当前应用版本信息
Future<Map<String, String>> getAppInfo() async {
return await _plugin.getAppInfo();
}
/// 显示升级对话框
Future<void> _showUpgradeDialog({
required BuildContext context,
required UpgradeInfo info,
required bool autoDownload,
required bool autoInstall,
VoidCallback? onComplete,
UpgradeConfig? config,
}) {
final effectiveConfig = config ?? _config;
return showDialog(
context: context,
barrierDismissible: !info.isForceUpdate,
builder: (context) {
if (info.isForceUpdate) {
return _ForceUpgradeDialog(
info: info,
config: effectiveConfig,
);
} else {
return _SimpleUpgradeDialog(
info: info,
autoDownload: autoDownload,
autoInstall: autoInstall,
onComplete: onComplete,
config: effectiveConfig,
showToast: (message) => _showToast(message, effectiveConfig),
);
}
},
);
}
/// 显示Toast提示
void _showToast(String message, [UpgradeConfig? config]) {
final effectiveConfig = config ?? _config;
if (effectiveConfig.customToast != null) {
effectiveConfig.customToast!(message);
} else {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 14.0,
);
}
}
/// 检查是否存在可用的 Material 环境(仅用于对话框)
bool _canShowMaterialDialog(BuildContext context) {
if (!context.mounted) return false;
try {
// Localizations.of will throw if not found.
return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations) != null;
} catch (_) {
// If it throws, it means we're not in a Material scope.
return false;
}
}
}
/// 共享的升级操作逻辑
mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
final _plugin = _SimpleAppUpgradePlugin.instance;
bool _isDownloading = false;
double _downloadProgress = 0;
String _statusText = '';
String? _downloadedFilePath; // 保存下载完成的APK文件路径
bool _isInstalling = false; // 是否正在安装
bool _isWaitingForInstallation = false; // 是否在等待用户完成安装
Timer? _installCheckTimer; // 安装检测定时器
UpgradeInfo get info;
void Function(String) get showToast;
VoidCallback? get onComplete;
bool get autoDownload;
bool get autoInstall;
UpgradeConfig get config;
void initUpgradeLogic() {
if (autoDownload && Platform.isAndroid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_startDownloadAndInstall();
}
});
}
}
void disposeUpgradeLogic() {
_installCheckTimer?.cancel();
}
void onAppLifecycleStateChanged(AppLifecycleState state) {
debugPrint('🔄 应用生命周期状态变化: $state, _isWaitingForInstallation=$_isWaitingForInstallation');
// 核心逻辑:当应用从后台回到前台时,检查安装状态
if (_isWaitingForInstallation && state == AppLifecycleState.resumed) {
debugPrint('⚡ 应用回到前台,检查安装状态');
// 延迟检测,让系统状态稳定
Future.delayed(const Duration(milliseconds: 1500), () {
if (mounted && _isWaitingForInstallation) {
_checkInstallationResult();
}
});
}
}
Future<void> _startDownloadAndInstall() async {
if (!Platform.isAndroid || info.downloadUrl == null) return;
if (!mounted) return;
final hasStorage = await PermissionHelper.checkAndRequestStoragePermission(context: context);
if (!hasStorage) {
showToast('缺少存储权限,无法下载');
return;
}
await PermissionHelper.checkAndRequestNotificationPermission(context: context);
setState(() {
_isDownloading = true;
_statusText = '下载中...';
_downloadProgress = 0;
});
final filePath = await _plugin.downloadApk(
info.downloadUrl!,
onProgress: (p) {
if (!mounted) return;
setState(() {
_downloadProgress = p.progress;
_statusText = '下载中 ${p.percentage}%';
});
},
);
if (!mounted) return;
if (filePath == null) {
setState(() {
_isDownloading = false;
_statusText = '下载失败';
});
return;
}
setState(() {
_statusText = '下载完成';
_downloadProgress = 1.0;
_downloadedFilePath = filePath; // 保存文件路径
});
if (autoInstall) {
await _installApk(filePath);
}
}
Future<void> _installApk(String filePath) async {
if (!mounted) return;
setState(() {
_isInstalling = true;
_statusText = '准备安装...';
});
// 根据配置决定是否需要检查权限
if (config.requireInstallPermission) {
debugPrint('🔐 检查安装权限(配置要求)');
final hasPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context);
if (!hasPermission) {
if (mounted) {
setState(() {
_isInstalling = false;
_statusText = '权限被拒绝';
});
showToast('未授予安装权限,无法完成更新');
}
return;
}
} else {
debugPrint('🚀 跳过权限检查,直接安装(配置默认)');
}
if (mounted) {
setState(() {
_statusText = '正在安装...';
});
}
try {
final success = await _plugin.installApk(filePath);
if (mounted) {
if (success) {
// 成功调起安装界面,开始等待用户操作
setState(() {
_isInstalling = false;
_isWaitingForInstallation = true;
_statusText = '请完成安装';
});
showToast('请在系统弹窗中完成安装');
// 启动定时检测(备用方案)
_startInstallationTimeoutCheck();
} else {
setState(() {
_isInstalling = false;
_statusText = '安装失败';
});
showToast('无法启动安装程序,请重试');
}
}
} catch (e) {
debugPrint('安装APK异常: $e');
if (mounted) {
setState(() {
_isInstalling = false;
_statusText = '安装异常';
});
showToast('安装出现异常,请重试');
}
}
}
/// 启动简化的安装检测(主要依靠应用生命周期)
void _startInstallationTimeoutCheck() {
debugPrint('🚀 启动简化安装检测系统');
// 核心策略:主要依靠应用生命周期监听,减少定时检测
// 仅保留一个简单的超时检测
_installCheckTimer = Timer(Duration(seconds: config.installTimeout), () {
if (mounted && _isWaitingForInstallation) {
debugPrint('⏰ 安装检测超时');
setState(() {
_isWaitingForInstallation = false;
_statusText = '安装超时';
});
showToast('安装超时,请使用重试按钮重新安装');
}
});
}
/// 简化的安装结果检测(仅版本检测)
Future<void> _checkInstallationResult() async {
if (!mounted || !_isWaitingForInstallation) {
debugPrint('跳过安装结果检查: mounted=$mounted, _isWaitingForInstallation=$_isWaitingForInstallation');
return;
}
debugPrint('🔍 开始简化安装检测...');
try {
// 获取当前应用信息
final appInfo = await _plugin.getCurrentAppInfo();
final currentVersion = appInfo['version'] ?? '';
final currentBuildNumber = int.tryParse(appInfo['buildNumber'] ?? '0') ?? 0;
debugPrint('📱 当前版本: $currentVersion, 构建号: $currentBuildNumber');
debugPrint('🎯 目标版本: ${info.versionName}, 构建号: ${info.versionBuildNumber}');
// 优先比较构建号(更准确)
bool isUpdated = false;
if (info.versionBuildNumber > 0) {
isUpdated = currentBuildNumber >= info.versionBuildNumber;
debugPrint('📊 构建号比较: $currentBuildNumber >= ${info.versionBuildNumber} = $isUpdated');
} else {
// 备用方案:比较版本号
isUpdated = currentVersion == info.versionName;
debugPrint('📊 版本号比较: $currentVersion == ${info.versionName} = $isUpdated');
}
if (isUpdated) {
debugPrint('✅ 检测结果: 安装成功');
_handleInstallationSuccess();
} else {
debugPrint('❌ 检测结果: 安装被取消(版本未更新)');
_handleInstallationCancelled();
}
} catch (e) {
debugPrint('❌ 检测失败: $e');
setState(() {
_isWaitingForInstallation = false;
_statusText = '检测失败';
});
showToast('无法检测安装状态,请手动确认');
}
}
/// 处理安装成功
void _handleInstallationSuccess() {
_installCheckTimer?.cancel();
setState(() {
_isWaitingForInstallation = false;
_statusText = '安装成功';
});
showToast('应用更新成功!');
// 延迟关闭对话框
Future.delayed(const Duration(seconds: 1), () {
if (mounted && Navigator.canPop(context)) {
Navigator.of(context).pop();
}
onComplete?.call();
});
}
/// 处理安装被取消
void _handleInstallationCancelled() {
setState(() {
_isWaitingForInstallation = false;
_statusText = '安装被取消';
});
showToast('安装被取消,可以点击重试按钮重新安装');
}
/// 重新安装APK
Future<void> _retryInstall() async {
if (_downloadedFilePath != null) {
// 如果是"请完成安装"状态,先检查安装结果
if (_statusText == '请完成安装') {
await _checkInstallationResult();
// 如果检查后状态仍然是"请完成安装",则重新启动安装
if (_statusText == '请完成安装') {
await _installApk(_downloadedFilePath!);
}
return;
}
// 如果是权限被拒绝,且配置要求权限,先尝试重新请求权限
if (_statusText == '权限被拒绝' && config.requireInstallPermission) {
final hasPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context);
if (!hasPermission) {
showToast('仍未获得安装权限,请在设置中手动开启');
return;
}
// 权限获得后,重置状态并重新安装
if (mounted) {
setState(() {
_statusText = '下载完成';
});
}
}
await _installApk(_downloadedFilePath!);
}
}
/// 构建版本信息卡片
Widget _buildVersionInfoCard(BuildContext context, ColorScheme colorScheme) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primaryContainer.withOpacity(0.3),
colorScheme.primaryContainer.withOpacity(0.1),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: colorScheme.outline.withOpacity(0.2),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 新版本号 - 主要信息
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'v${info.versionName}',
style: TextStyle(
color: colorScheme.onPrimary,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 8),
if (info.isForceUpdate)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: colorScheme.error,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'强制',
style: TextStyle(
color: colorScheme.onError,
fontSize: 10,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 8),
// 版本对比和大小信息
Row(
children: [
Expanded(
child: _buildInfoChip(
context,
icon: Icons.update,
label: '当前版本',
value: info.currentVersionName,
colorScheme: colorScheme,
),
),
if (info.apkSize != null) ...[
const SizedBox(width: 12),
Expanded(
child: _buildInfoChip(
context,
icon: Icons.file_download,
label: '安装包大小',
value: formatBytes(info.apkSize!),
colorScheme: colorScheme,
),
),
],
],
),
],
),
);
}
/// 构建信息芯片
Widget _buildInfoChip(
BuildContext context, {
required IconData icon,
required String label,
required String value,
required ColorScheme colorScheme,
}) {
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: colorScheme.outline.withOpacity(0.2),
width: 0.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
icon,
size: 12,
color: colorScheme.onSurface.withOpacity(0.6),
),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
color: colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
const SizedBox(height: 2),
Text(
value,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
),
],
),
);
}
/// 构建更新内容
Widget _buildUpdateContent(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final changeItems =
info.updateContent.split(RegExp(r'\r?\n')).map((e) => e.trim()).where((e) => e.isNotEmpty).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.new_releases_outlined,
size: 16,
color: colorScheme.primary,
),
const SizedBox(width: 6),
Text(
'更新内容',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxHeight: 200),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: colorScheme.outline.withOpacity(0.2),
width: 0.5,
),
),
child: SingleChildScrollView(
child: changeItems.isEmpty
? _buildRichText(
info.updateContent,
colorScheme,
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: changeItems.asMap().entries.map((entry) {
final index = entry.key;
final line = entry.value;
return Container(
width: double.infinity,
margin: EdgeInsets.only(
bottom: index < changeItems.length - 1 ? 8 : 0,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 6),
width: 4,
height: 4,
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildRichText(
line,
colorScheme,
),
),
],
),
);
}).toList(),
),
),
),
],
);
}
/// 构建富文本内容
Widget _buildRichText(String content, ColorScheme colorScheme) {
// 解析富文本标记
final spans = <TextSpan>[];
final regex = RegExp(r'\*\*(.*?)\*\*|__(.*?)__|`(.*?)`|\[(.*?)\]');
int lastIndex = 0;
for (final match in regex.allMatches(content)) {
// 添加普通文本
if (match.start > lastIndex) {
spans.add(TextSpan(
text: content.substring(lastIndex, match.start),
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
),
));
}
// 添加格式化文本
if (match.group(1) != null) {
// **粗体**
spans.add(TextSpan(
text: match.group(1),
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontWeight: FontWeight.bold,
),
));
} else if (match.group(2) != null) {
// __斜体__
spans.add(TextSpan(
text: match.group(2),
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontStyle: FontStyle.italic,
),
));
} else if (match.group(3) != null) {
// `代码`
spans.add(TextSpan(
text: match.group(3),
style: TextStyle(
fontSize: 12,
color: colorScheme.primary,
height: 1.4,
fontFamily: 'monospace',
backgroundColor: colorScheme.primaryContainer.withOpacity(0.2),
),
));
} else if (match.group(4) != null) {
// [重要内容]
spans.add(TextSpan(
text: match.group(4),
style: TextStyle(
fontSize: 13,
color: colorScheme.primary,
height: 1.4,
fontWeight: FontWeight.w600,
),
));
}
lastIndex = match.end;
}
// 添加剩余的普通文本
if (lastIndex < content.length) {
spans.add(TextSpan(
text: content.substring(lastIndex),
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
),
));
}
// 如果没有富文本标记,返回普通文本
if (spans.isEmpty) {
return Text(
content,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
),
softWrap: true,
maxLines: null,
);
}
return RichText(
text: TextSpan(children: spans),
softWrap: true,
maxLines: null,
);
}
/// 构建增强的下载进度UI
Widget _buildEnhancedDownloadProgress(BuildContext context, ColorScheme colorScheme) {
// 判断是否显示重新安装按钮 - 在以下情况显示:
// 1. 有下载完成的文件路径
// 2. 不在下载状态
// 3. 不在安装状态(或者不在等待安装状态)
// 4. 下载进度已完成
// 5. 状态不是"安装成功"
final bool showRetryButton = _downloadedFilePath != null &&
!_isDownloading &&
!_isInstalling &&
!_isWaitingForInstallation &&
_downloadProgress >= 1.0 &&
_statusText != '安装成功';
// 调试信息
debugPrint('重试按钮显示条件检查:');
debugPrint(' _downloadedFilePath != null: ${_downloadedFilePath != null}');
debugPrint(' !_isDownloading: ${!_isDownloading}');
debugPrint(' !_isInstalling: ${!_isInstalling}');
debugPrint(' !_isWaitingForInstallation: ${!_isWaitingForInstallation}');
debugPrint(' _downloadProgress >= 1.0: ${_downloadProgress >= 1.0}');
debugPrint(' _statusText != "安装成功": ${_statusText != '安装成功'}');
debugPrint(' showRetryButton: $showRetryButton');
debugPrint(' 当前状态: $_statusText');
return Column(
children: [
const SizedBox(height: 20),
GestureDetector(
onTap: _shouldShowRetryOptions() ? _retryInstall : null,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: colorScheme.primary.withOpacity(0.2),
width: 1,
),
// 如果可以重试,添加点击效果
boxShadow: _shouldShowRetryOptions()
? [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
]
: null,
),
child: Column(
children: [
// 下载状态图标和文本
Row(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _getStatusColor(colorScheme).withOpacity(0.1),
shape: BoxShape.circle,
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Icon(
_getStatusIcon(),
key: ValueKey(_statusText),
color: _getStatusColor(colorScheme),
size: 20,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
_statusText,
key: ValueKey(_statusText),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
),
),
const SizedBox(height: 2),
if (_isDownloading || _downloadProgress < 1.0)
Text(
'${(_downloadProgress * 100).toStringAsFixed(1)}%',
style: TextStyle(
fontSize: 12,
color: colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
// 添加点击提示(当有错误状态或等待用户操作时)
if (_shouldShowRetryOptions())
Text(
_getClickHintText(),
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurface.withOpacity(0.6),
),
),
// 为"请完成安装"状态添加简单提示
if (_statusText == '请完成安装')
Padding(
padding: const EdgeInsets.only(top: 12),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 1,
),
),
child: Column(
children: [
Icon(
Icons.touch_app,
color: colorScheme.primary,
size: 24,
),
const SizedBox(height: 8),
Text(
'请在系统安装界面完成操作',
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
'系统将自动检测安装结果',
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
),
),
),
],
),
),
// 重新安装按钮 - 强制在特定状态下显示
if (_shouldShowRetryOptions())
Container(
margin: const EdgeInsets.only(left: 8),
child: ElevatedButton.icon(
onPressed: _retryInstall,
icon: Icon(_getRetryButtonIcon(), size: 16),
label: Text(_getRetryButtonText(), style: const TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: _getRetryButtonColor(colorScheme),
),
),
),
],
),
// 进度条(只在下载时显示)
if (_isDownloading || (_downloadProgress > 0 && _downloadProgress < 1.0)) ...[
const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: [
// 背景进度条
Container(
height: 8,
width: double.infinity,
decoration: BoxDecoration(
color: colorScheme.outline.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
),
// 进度条
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
height: 8,
width: double.infinity,
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: _downloadProgress.clamp(0.0, 1.0),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primary,
colorScheme.primary.withOpacity(0.8),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(4),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
),
),
),
],
),
),
],
],
),
),
),
],
);
}
/// 获取状态图标
IconData _getStatusIcon() {
if (_isInstalling) {
return Icons.install_mobile;
} else if (_isDownloading) {
return Icons.download_for_offline;
} else if (_statusText == '安装成功') {
return Icons.check_circle;
} else if (_statusText == '请完成安装') {
return Icons.touch_app;
} else if (_statusText == '等待安装中') {
return Icons.hourglass_bottom;
} else if (_statusText == '等待确认中') {
return Icons.help_outline;
} else if (_statusText == '等待超时') {
return Icons.timer_off;
} else if (_statusText == '安装被取消') {
return Icons.cancel_outlined;
} else if (_statusText == '安装超时' || _statusText == '检测失败') {
return Icons.schedule;
} else if (_statusText == '安装失败' || _statusText == '安装异常' || _statusText == '权限被拒绝') {
return Icons.error_outline;
} else if (_downloadProgress >= 1.0) {
return Icons.check_circle_outline;
} else {
return Icons.download_for_offline;
}
}
/// 获取状态颜色
Color _getStatusColor(ColorScheme colorScheme) {
if (_isInstalling) {
return colorScheme.secondary;
} else if (_statusText == '安装成功') {
return colorScheme.tertiary;
} else if (_statusText == '请完成安装') {
return colorScheme.secondary;
} else if (_statusText == '等待安装中') {
return colorScheme.secondary.withOpacity(0.9);
} else if (_statusText == '等待确认中') {
return Colors.orange;
} else if (_statusText == '等待超时') {
return colorScheme.error.withOpacity(0.7);
} else if (_statusText == '安装被取消') {
return colorScheme.secondary.withOpacity(0.8);
} else if (_statusText == '安装超时' || _statusText == '检测失败') {
return colorScheme.secondary.withOpacity(0.7);
} else if (_statusText == '安装失败' || _statusText == '安装异常' || _statusText == '权限被拒绝') {
return colorScheme.error;
} else if (_downloadProgress >= 1.0) {
return colorScheme.tertiary;
} else {
return colorScheme.primary;
}
}
/// 获取重试按钮图标
IconData _getRetryButtonIcon() {
if (_statusText == '权限被拒绝' && config.requireInstallPermission) {
return Icons.settings;
} else if (_statusText == '安装失败' ||
_statusText == '安装异常' ||
_statusText == '安装超时' ||
_statusText == '安装被取消' ||
_statusText == '检测失败' ||
_statusText == '等待安装中' ||
_statusText == '等待确认中' ||
_statusText == '等待超时') {
return Icons.refresh;
} else if (_statusText == '请完成安装') {
return Icons.launch;
} else {
return Icons.install_mobile;
}
}
/// 获取重试按钮文本
String _getRetryButtonText() {
if (_statusText == '权限被拒绝' && config.requireInstallPermission) {
return '设置';
} else if (_statusText == '安装失败' ||
_statusText == '安装异常' ||
_statusText == '安装超时' ||
_statusText == '安装被取消' ||
_statusText == '检测失败' ||
_statusText == '等待超时') {
return '重试';
} else if (_statusText == '等待安装中' || _statusText == '等待确认中') {
return '重新安装';
} else if (_statusText == '请完成安装') {
return '重新安装';
} else {
return '安装';
}
}
/// 获取重试按钮颜色
Color? _getRetryButtonColor(ColorScheme colorScheme) {
if (_statusText == '权限被拒绝') {
return colorScheme.secondary;
} else if (_statusText == '安装失败' || _statusText == '安装异常') {
return colorScheme.error;
} else if (_statusText == '安装超时' || _statusText == '安装被取消' || _statusText == '检测失败' || _statusText == '等待安装中') {
return colorScheme.secondary.withOpacity(0.8);
} else if (_statusText == '请完成安装') {
return colorScheme.secondary;
} else {
return null; // 使用默认颜色
}
}
/// 判断是否应该显示重试选项
bool _shouldShowRetryOptions() {
return _statusText == '安装被取消' ||
_statusText == '安装失败' ||
_statusText == '安装异常' ||
(_statusText == '权限被拒绝' && config.requireInstallPermission) ||
_statusText == '安装超时' ||
_statusText == '检测失败' ||
_statusText == '等待安装中' ||
_statusText == '等待确认中' ||
_statusText == '等待超时' ||
(_downloadedFilePath != null &&
!_isDownloading &&
!_isInstalling &&
!_isWaitingForInstallation &&
_downloadProgress >= 1.0 &&
_statusText != '安装成功');
}
/// 获取点击提示文本
String _getClickHintText() {
switch (_statusText) {
case '请完成安装':
return '完成后请点击确认按钮';
case '等待安装中':
return '点击重新安装';
case '等待确认中':
return '请点击确认按钮';
case '等待超时':
return '点击重新安装';
case '安装被取消':
return '点击重新安装';
case '权限被拒绝':
return config.requireInstallPermission ? '点击打开设置' : '点击重试';
case '安装超时':
return '点击重新尝试';
default:
return '点击区域重试';
}
}
void _handleAction() {
if (Platform.isAndroid) {
_handleAndroidAction();
} else if (Platform.isIOS) {
_handleIosAction();
} else {
showToast('Unsupported platform');
}
}
/// Handles the upgrade action for iOS.
void _handleIosAction() {
if (info.appStoreUrl != null) {
_plugin.goToAppStore(info.appStoreUrl!);
// Pop the upgrade dialog.
if (Navigator.canPop(context)) {
Navigator.of(context).pop();
}
onComplete?.call();
} else {
showToast('App Store URL is not available.');
}
}
/// Handles the upgrade action for Android.
Future<void> _handleAndroidAction() async {
// On Android, we always assume a market option is available,
// because we can fall back to a generic market:// intent.
final bool hasDownloadOption = info.downloadUrl != null;
// 在Android上我们总是假设市场选项可用因为可以回退到通用的 market:// intent
// 因此这里不需要检查无更新方法的情况
// If a download option is not available, going to the market is the only choice.
if (!hasDownloadOption) {
_handleMarketAction();
return;
}
// If a download option exists, always give the user a choice,
// as the market option is also implicitly available.
await _showDownloadChoiceSheet();
}
/// Opens the app store or shows a market selection dialog.
Future<void> _performMarketAction() async {
final hasMarkets = info.appMarkets?.isNotEmpty ?? false;
if (hasMarkets) {
await MarketSelectionDialog.show(
context,
markets: info.appMarkets!,
onSelected: (market) {
_plugin.goToAppStore(market.url ?? market.packageName ?? '');
},
);
} else {
// No specific markets, try a generic market link.
final appInfo = await _plugin.getAppInfo();
final pkg = appInfo['packageName'] ?? '';
if (pkg.isNotEmpty) {
_plugin.goToAppStore('market://details?id=$pkg');
} else {
showToast('Could not determine app package name.');
}
}
}
/// Pops the current dialog and then performs the market action.
Future<void> _handleMarketAction() async {
// 对于强制更新,不关闭对话框
if (!info.isForceUpdate && Navigator.canPop(context)) {
Navigator.of(context).pop();
}
if (!mounted) return;
await _performMarketAction();
onComplete?.call();
}
Future<void> _showDownloadChoiceSheet() async {
if (!mounted) return;
final choice = await showModalBottomSheet<String>(
context: context,
isScrollControlled: false,
useRootNavigator: true,
isDismissible: !info.isForceUpdate, // 强制更新时不允许通过手势关闭
enableDrag: !info.isForceUpdate, // 强制更新时不允许拖拽关闭
// UI Beautification: Rounded corners
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (ctx) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Draggable handle
Container(
width: 40,
height: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
// Title and Close Button - 强制更新时不显示关闭按钮
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Stack(
alignment: Alignment.center,
children: [
const Text('选择更新方式', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
if (!info.isForceUpdate)
Positioned(
right: -12,
child: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(ctx).pop(),
),
),
],
),
),
// Option 1: App Market
ListTile(
leading: const Icon(Icons.storefront_outlined),
title: const Text('前往应用市场更新'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
onTap: () => Navigator.of(ctx).pop('market'),
),
// Option 2: Direct Download
ListTile(
leading: const Icon(Icons.download_for_offline_outlined),
title: const Text('直接下载安装包'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
onTap: () => Navigator.of(ctx).pop('download'),
),
const Divider(height: 24),
// Cancel Button - 强制更新时不显示取消按钮
if (!info.isForceUpdate)
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor: Colors.white,
foregroundColor: Theme.of(context).textTheme.bodyLarge?.color,
elevation: 2,
shadowColor: Colors.grey.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.grey.shade300),
),
),
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('取消', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
),
),
],
),
),
);
},
);
if (choice == 'market') {
// 对于强制更新,不调用 onComplete保持对话框打开状态
if (!info.isForceUpdate) {
await _performMarketAction();
onComplete?.call();
} else {
await _performMarketAction();
}
return;
}
if (choice == 'download' && !_isDownloading) {
await _startDownloadAndInstall();
}
}
}
/// 简化版升级对话框(非强制更新)
class _SimpleUpgradeDialog extends StatefulWidget {
final UpgradeInfo info;
final bool autoDownload;
final bool autoInstall;
final VoidCallback? onComplete;
final void Function(String) showToast;
final UpgradeConfig config;
const _SimpleUpgradeDialog({
required this.info,
required this.autoDownload,
required this.autoInstall,
this.onComplete,
required this.showToast,
required this.config,
});
@override
State<_SimpleUpgradeDialog> createState() => _SimpleUpgradeDialogState();
}
class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _UpgradeDialogLogic, WidgetsBindingObserver {
@override
UpgradeInfo get info => widget.info;
@override
void Function(String) get showToast => widget.showToast;
@override
VoidCallback? get onComplete => widget.onComplete;
@override
bool get autoDownload => widget.autoDownload;
@override
bool get autoInstall => widget.autoInstall;
@override
UpgradeConfig get config => widget.config;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
initUpgradeLogic();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
disposeUpgradeLogic();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
onAppLifecycleStateChanged(state);
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
child: Container(
width: 320, // 固定宽度
constraints: const BoxConstraints(
maxHeight: 600, // 最大高度限制
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 标题栏
Container(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
child: Row(
children: [
Icon(
Icons.system_update,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
const Expanded(
child: Text(
'发现新版本',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
// 内容区域
Flexible(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
// 版本信息卡片
_buildVersionInfoCard(context, Theme.of(context).colorScheme),
const SizedBox(height: 16),
// 更新内容
_buildUpdateContent(context),
// 下载进度 - 使用增强版UI
if (_isDownloading || _downloadedFilePath != null)
_buildEnhancedDownloadProgress(context, Theme.of(context).colorScheme),
],
),
),
),
// 按钮区域
if (!_isDownloading)
Container(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
widget.onComplete?.call();
},
child: const Text('稍后更新'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _handleAction,
child: Text(Platform.isAndroid ? '立即更新' : '前往更新'),
),
],
),
),
],
),
),
);
}
}
/// 强制更新对话框
class _ForceUpgradeDialog extends StatefulWidget {
final UpgradeInfo info;
final UpgradeConfig config;
const _ForceUpgradeDialog({
required this.info,
required this.config,
});
@override
State<_ForceUpgradeDialog> createState() => _ForceUpgradeDialogState();
}
class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> with _UpgradeDialogLogic, WidgetsBindingObserver {
@override
UpgradeInfo get info => widget.info;
@override
void Function(String) get showToast => (message) => AppUpgradeSimple.instance._showToast(message, widget.config);
@override
VoidCallback? get onComplete => null;
@override
bool get autoDownload => false;
@override
bool get autoInstall => true;
@override
UpgradeConfig get config => widget.config;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
initUpgradeLogic();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
disposeUpgradeLogic();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
onAppLifecycleStateChanged(state);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false, // 强制更新,不允许返回
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
child: Container(
width: 320, // 固定宽度
constraints: const BoxConstraints(
maxHeight: 600, // 最大高度限制
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 标题栏
Container(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.warning_amber_rounded,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 8),
const Expanded(
child: Text(
'发现新版本 (强制)',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
],
),
),
// 内容区域
Flexible(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
// 版本信息卡片
_buildVersionInfoCard(context, Theme.of(context).colorScheme),
const SizedBox(height: 16),
// 更新内容
_buildUpdateContent(context),
// 下载进度 - 使用增强版UI
if (_isDownloading || _downloadedFilePath != null)
_buildEnhancedDownloadProgress(context, Theme.of(context).colorScheme),
],
),
),
),
// 按钮区域
if (!_isDownloading)
Container(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _handleAction,
child: Text(Platform.isAndroid ? '立即更新' : '前往更新'),
),
),
),
],
),
),
),
);
}
}