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 checkUpdate(String url, {Map? params}) { return AppUpgradePluginPlatform.instance.checkUpdate(url, params: params); } Future downloadApk(String url, {Function(DownloadProgress)? onProgress}) { return AppUpgradePluginPlatform.instance.downloadApk(url, onProgress: onProgress); } Future installApk(String filePath) { return AppUpgradePluginPlatform.instance.installApk(filePath); } Future goToAppStore(String url) { return AppUpgradePluginPlatform.instance.goToAppStore(url); } Future> getAppInfo() { return AppUpgradePluginPlatform.instance.getAppInfo(); } /// 检查当前应用的版本信息(用于安装状态检测) Future> getCurrentAppInfo() async { try { return await AppUpgradePluginPlatform.instance.getAppInfo(); } catch (e) { debugPrint('获取应用信息失败: $e'); return {}; } } /// 获取下载路径 Future getDownloadPath() { return AppUpgradePluginPlatform.instance.getDownloadPath(); } /// 精确检测应用是否已安装(通过包名) Future 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 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 checkNetworkStatus() async { try { final result = await InternetAddress.lookup('baidu.com'); return result.isNotEmpty && result[0].rawAddress.isNotEmpty; } catch (e) { debugPrint('网络检查失败: $e'); return false; } } /// 清理下载的临时文件 Future 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 checkUpdate({ required BuildContext context, required String url, Map? 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 checkUpdateSilent({ required String url, Map? params, }) async { try { return await _plugin.checkUpdate(url, params: params); } catch (e) { if (_config.enableDebugLog) { debugPrint('静默检查更新失败: $e'); } return null; } } /// 预下载APK(不显示UI,后台下载) Future 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 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> getAppInfo() async { return await _plugin.getAppInfo(); } /// 显示升级对话框 Future _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(context, MaterialLocalizations) != null; } catch (_) { // If it throws, it means we're not in a Material scope. return false; } } } /// 共享的升级操作逻辑 mixin _UpgradeDialogLogic on State { 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 _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 _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 _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 _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 = []; 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 _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 _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 _handleMarketAction() async { // 对于强制更新,不关闭对话框 if (!info.isForceUpdate && Navigator.canPop(context)) { Navigator.of(context).pop(); } if (!mounted) return; await _performMarketAction(); onComplete?.call(); } Future _showDownloadChoiceSheet() async { if (!mounted) return; final choice = await showModalBottomSheet( 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 ? '立即更新' : '前往更新'), ), ), ), ], ), ), ), ); } }