diff --git a/lib/app_upgrade_plugin.dart b/lib/app_upgrade_plugin.dart index 2291a5f..d4af53e 100644 --- a/lib/app_upgrade_plugin.dart +++ b/lib/app_upgrade_plugin.dart @@ -16,6 +16,7 @@ export 'core/http_config.dart'; export 'core/permission_helper.dart'; export 'models/install_strategy.dart'; export 'models/upgrade_info.dart'; +// 导出市场选择对话框(其他对话框已整合到简化API中) export 'widgets/widgets.dart'; class AppUpgradePlugin { diff --git a/lib/app_upgrade_plugin_enhanced.dart b/lib/app_upgrade_plugin_enhanced.dart deleted file mode 100644 index dd18772..0000000 --- a/lib/app_upgrade_plugin_enhanced.dart +++ /dev/null @@ -1,638 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; // Added for MethodChannel -import 'package:flutter/widgets.dart'; // Added for WidgetsBinding -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'app_upgrade_plugin_platform_interface.dart'; -import 'core/cache_manager.dart'; -import 'core/download_manager.dart'; -import 'core/network_monitor.dart'; -import 'core/notification_helper.dart'; -import 'core/upgrade_config.dart'; -import 'core/version_comparator.dart'; -import 'models/upgrade_info.dart'; - -export 'core/cache_manager.dart'; -export 'core/download_manager.dart'; -export 'core/network_monitor.dart'; -export 'core/upgrade_config.dart'; -export 'core/version_comparator.dart'; -export 'models/upgrade_info.dart'; -export 'widgets/widgets.dart'; - -/// 升级回调 -typedef UpgradeCallback = void Function(UpgradeInfo info); -typedef DownloadCallback = void Function(DownloadTask task); -typedef ErrorCallback = void Function(String error); - -/// 增强版App升级插件 -class AppUpgradePluginEnhanced { - static AppUpgradePluginEnhanced? _instance; - - /// 获取单例实例 - static AppUpgradePluginEnhanced get instance { - _instance ??= AppUpgradePluginEnhanced._(); - return _instance!; - } - - AppUpgradePluginEnhanced._() { - _init(); - } - // 核心组件 - - final UpgradeConfig _config = UpgradeConfig.instance; - final DownloadManager _downloadManager = DownloadManager.instance; - final NetworkMonitor _networkMonitor = NetworkMonitor.instance; - final CacheManager _cacheManager = CacheManager.instance; - final NotificationHelper _notificationHelper = NotificationHelper.instance; - late VersionComparator _versionComparator; - final Dio _dio = Dio(); - - // 回调 - final List _upgradeCallbacks = []; - final List _downloadCallbacks = []; - final List _errorCallbacks = []; - - // 状态 - UpgradeInfo? _currentUpgradeInfo; - String? _currentDownloadTaskId; - Timer? _autoCheckTimer; - StreamSubscription? _networkSubscription; - - // 内存管理 - final List _subscriptions = []; - final Map> _weakRefs = {}; - - /// 初始化 - void _init() { - _versionComparator = VersionComparator( - strategy: _config.debugMode ? VersionCompareStrategy.buildNumber : VersionCompareStrategy.semantic, - ); - - // 延迟初始化Flutter相关服务,直到绑定完全初始化 - _scheduleFlutterServicesInit(); - - // 监听网络状态 - 这个可以立即初始化,因为不依赖Flutter绑定 - _networkSubscription = _networkMonitor.statusStream.listen(_onNetworkStatusChanged); - _subscriptions.add(_networkSubscription!); - - // 启动自动检查 - if (_config.autoCheck) { - _startAutoCheck(); - } - } - - /// 延迟初始化Flutter相关服务 - void _scheduleFlutterServicesInit() { - // 如果绑定已经初始化,直接执行 - _initFlutterServices(); - } - - /// 初始化Flutter相关服务 - void _initFlutterServices() { - try { - // 初始化通知,并设置点击回调 - _notificationHelper.initialize(_onNotificationClick); - - // 监听原生渠道的安装请求 - const MethodChannel('app_upgrade_plugin_channel').setMethodCallHandler((call) async { - if (call.method == 'installApkFromNotification') { - final filePath = call.arguments as String?; - if (filePath != null) { - await installApkSmart(filePath); - } - } - }); - } catch (e) { - debugPrint('Failed to initialize Flutter services: $e'); - } - } - - /// 配置插件 - void configure({ - bool? debugMode, - int? checkIntervalHours, - bool? autoCheck, - bool? wifiOnly, - int? downloadTimeout, - int? connectTimeout, - int? maxRetryCount, - bool? supportBreakpoint, - bool? verifyIntegrity, - VersionCompareStrategy? versionStrategy, - Map? customHeaders, - }) { - _config.updateConfig( - debugMode: debugMode, - checkIntervalHours: checkIntervalHours, - autoCheck: autoCheck, - wifiOnly: wifiOnly, - downloadTimeout: downloadTimeout, - connectTimeout: connectTimeout, - maxRetryCount: maxRetryCount, - supportBreakpoint: supportBreakpoint, - verifyIntegrity: verifyIntegrity, - customHeaders: customHeaders, - ); - - if (versionStrategy != null) { - _versionComparator = VersionComparator(strategy: versionStrategy); - } - - // 重启自动检查 - if (autoCheck != null) { - if (autoCheck) { - _startAutoCheck(); - } else { - _stopAutoCheck(); - } - } - } - - /// 获取平台版本(无需平台接口) - Future getPlatformVersion() async { - try { - final info = await PackageInfo.fromPlatform(); - return info.version; - } catch (e) { - debugPrint('getPlatformVersion failed: $e'); - return null; - } - } - - /// 获取当前App信息 - Future> getAppInfo() async { - return await _cacheManager.get>( - 'app_info', - strategy: CacheStrategy.cacheFirst, - networkFetcher: _getPackageInfoMap, - ) ?? - {}; - } - - /// 检查更新(智能版) - Future checkUpdateSmart( - String url, { - Map? params, - bool forceRefresh = false, - Duration? cacheDuration, - }) async { - try { - // 检查网络状态 - if (!_networkMonitor.isConnected) { - _notifyError('No network connection'); - return null; - } - - // 检查是否可以下载 - if (_config.wifiOnly && !_networkMonitor.isWifi) { - _notifyError('WiFi required for update check'); - return null; - } - - // 使用缓存策略 - final strategy = forceRefresh ? CacheStrategy.networkFirst : CacheStrategy.cacheFirst; - - final upgradeInfo = await _cacheManager.get( - 'upgrade_info_$url', - strategy: strategy, - networkFetcher: () async { - final info = await _fetchUpgradeInfo(url, params: params); - if (info != null) { - // 智能版本比较 - final appInfo = await getAppInfo(); - final currentVersion = appInfo['version'] ?? '0.0.0'; - - if (_versionComparator.isUpdateAvailable(currentVersion, info.versionName)) { - // 判断更新类型 - final isMajor = _versionComparator.isMajorUpdate(currentVersion, info.versionName); - - // 根据更新类型调整策略 - if (isMajor && !info.isForceUpdate) { - // 主要版本更新建议强制 - debugPrint('Major update detected, suggesting force update'); - } - - return info; - } - } - return null; - }, - ); - - if (upgradeInfo != null) { - _currentUpgradeInfo = upgradeInfo; - _notifyUpgrade(upgradeInfo); - - // 预下载(如果配置了静默下载) - if (_config.silentDownload && upgradeInfo.downloadUrl != null && _networkMonitor.isWifi) { - _startSilentDownload(upgradeInfo.downloadUrl!); - } - } - - return upgradeInfo; - } catch (e) { - _notifyError('Check update failed: $e'); - return null; - } - } - - Future> _getPackageInfoMap() async { - final packageInfo = await PackageInfo.fromPlatform(); - return { - 'appName': packageInfo.appName, - 'packageName': packageInfo.packageName, - 'version': packageInfo.version, - 'buildNumber': packageInfo.buildNumber, - }; - } - - /// 直接通过网络请求拉取升级信息,避免依赖平台接口实现 - Future _fetchUpgradeInfo( - String url, { - Map? params, - }) async { - try { - final appInfo = await getAppInfo(); - final requestParams = { - 'version': appInfo['version'], - 'buildNumber': appInfo['buildNumber'], - 'platform': Platform.isAndroid ? 'android' : 'ios', - ...?params, - }; - - final response = await _dio.get(url, queryParameters: requestParams); - if (response.statusCode == 200 && response.data != null) { - return UpgradeInfo.fromJson(response.data); - } - return null; - } catch (e) { - debugPrint('fetch upgrade info failed: $e'); - return null; - } - } - - /// 智能下载APK - Future downloadApkSmart( - String url, { - String? versionName, // 新增版本名,用于标准化文件名 - Function(DownloadProgress)? onProgress, - String? savePath, - String? md5, - String? sha256, - bool resumeIfExists = true, - }) async { - if (!Platform.isAndroid) { - throw UnsupportedError('downloadApk only supports Android platform'); - } - - try { - // 检查网络状态 - final networkStatus = _networkMonitor.currentStatus; - if (networkStatus == null || !networkStatus.isConnected) { - _notifyError('No network connection for download'); - return null; - } - - // 创建下载任务 - _currentDownloadTaskId = await _downloadManager.createTask( - url: url, - savePath: savePath, - md5: md5, - sha256: sha256, - versionName: versionName ?? _currentUpgradeInfo?.versionName, - ); - - // 监听下载进度 - final progressSubscription = _downloadManager.getProgressStream(_currentDownloadTaskId!)?.listen((task) { - _notifyDownload(task); - - // 更新通知栏进度 - _updateNotificationForTask(task); - - if (onProgress != null && task.totalSize != null) { - onProgress(DownloadProgress(received: task.downloadedSize, total: task.totalSize!)); - } - }); - - if (progressSubscription != null) { - _subscriptions.add(progressSubscription); - } - - // 开始下载 - final success = await _downloadManager.startDownload(_currentDownloadTaskId!); - - if (success) { - final task = _downloadManager.getTask(_currentDownloadTaskId!); - return task?.savePath; - } - - return null; - } catch (e) { - _notifyError('Download failed: $e'); - return null; - } - } - - /// 安装APK(带权限检查) - Future installApkSmart(String filePath) async { - if (!Platform.isAndroid) { - return false; - } - - try { - // 检查文件是否存在 - final file = File(filePath); - if (!await file.exists()) { - _notifyError('APK file not found'); - return false; - } - - // 检查文件完整性(如果有MD5) - if (_currentUpgradeInfo?.apkMd5 != null) { - final isValid = await DownloadManager.verifyFileInIsolate({ - 'filePath': filePath, - 'md5': _currentUpgradeInfo!.apkMd5, - }); - - if (!isValid) { - _notifyError('APK file integrity check failed'); - return false; - } - } - - return await AppUpgradePluginPlatform.instance.installApk(filePath); - } catch (e) { - _notifyError('Install failed: $e'); - return false; - } - } - - /// 跳转到应用商店(直接使用 url_launcher,避免依赖平台通道) - Future goToAppStore(String url) async { - try { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - return await launchUrl(uri, mode: LaunchMode.externalApplication); - } - return false; - } catch (e) { - _notifyError('Failed to open app store: $e'); - return false; - } - } - - /// 暂停下载 - void pauseDownload() { - if (_currentDownloadTaskId != null) { - _downloadManager.pauseDownload(_currentDownloadTaskId!); - } - } - - /// 恢复下载 - Future resumeDownload() async { - if (_currentDownloadTaskId != null) { - return await _downloadManager.resumeDownload(_currentDownloadTaskId!); - } - return false; - } - - /// 取消下载 - void cancelDownload() { - if (_currentDownloadTaskId != null) { - _downloadManager.cancelDownload(_currentDownloadTaskId!); - _notificationHelper.cancelNotification(); // 取消时移除通知 - _currentDownloadTaskId = null; - } - } - - /// 重试下载 - Future retryDownload() async { - if (_currentDownloadTaskId != null) { - return await _downloadManager.retryDownload(_currentDownloadTaskId!); - } - return false; - } - - /// 获取下载任务 - DownloadTask? getCurrentDownloadTask() { - if (_currentDownloadTaskId != null) { - return _downloadManager.getTask(_currentDownloadTaskId!); - } - return null; - } - - /// 添加升级回调 - void addUpgradeCallback(UpgradeCallback callback) { - _upgradeCallbacks.add(callback); - } - - /// 移除升级回调 - void removeUpgradeCallback(UpgradeCallback callback) { - _upgradeCallbacks.remove(callback); - } - - /// 添加下载回调 - void addDownloadCallback(DownloadCallback callback) { - _downloadCallbacks.add(callback); - } - - /// 移除下载回调 - void removeDownloadCallback(DownloadCallback callback) { - _downloadCallbacks.remove(callback); - } - - /// 添加错误回调 - void addErrorCallback(ErrorCallback callback) { - _errorCallbacks.add(callback); - } - - /// 移除错误回调 - void removeErrorCallback(ErrorCallback callback) { - _errorCallbacks.remove(callback); - } - - /// 开始自动检查 - void _startAutoCheck() { - _stopAutoCheck(); - - _autoCheckTimer = Timer.periodic(Duration(hours: _config.checkIntervalHours), (_) async { - // 自动检查需要在WiFi环境下 - if (_config.wifiOnly && !_networkMonitor.isWifi) { - return; - } - - // 执行检查(使用缓存的URL) - final lastCheckUrl = await _cacheManager.get('last_check_url'); - if (lastCheckUrl != null) { - await checkUpdateSmart(lastCheckUrl); - } - }); - } - - /// 停止自动检查 - void _stopAutoCheck() { - _autoCheckTimer?.cancel(); - _autoCheckTimer = null; - } - - /// 静默下载 - void _startSilentDownload(String url) async { - if (!_config.silentDownload) return; - if (!_networkMonitor.isWifi) return; - - try { - final taskId = await _downloadManager.createTask(url: url); - await _downloadManager.startDownload(taskId); - } catch (e) { - debugPrint('Silent download failed: $e'); - } - } - - /// 网络状态变化处理 - void _onNetworkStatusChanged(NetworkStatus status) { - if (_config.debugMode) { - debugPrint('Network status changed: ${status.toJson()}'); - } - - // 网络恢复时恢复下载 - if (status.isConnected && _currentDownloadTaskId != null) { - final task = _downloadManager.getTask(_currentDownloadTaskId!); - if (task != null && task.status == DownloadStatus.paused) { - resumeDownload(); - } - } - - // WiFi环境下启动静默下载 - if (status.type == NetworkType.wifi && _config.silentDownload && _currentUpgradeInfo?.downloadUrl != null) { - _startSilentDownload(_currentUpgradeInfo!.downloadUrl!); - } - } - - /// 通知升级信息 - void _notifyUpgrade(UpgradeInfo info) { - for (final callback in _upgradeCallbacks) { - callback(info); - } - } - - /// 通知下载进度 - void _notifyDownload(DownloadTask task) { - for (final callback in _downloadCallbacks) { - callback(task); - } - } - - /// 通知错误 - void _notifyError(String error) { - if (_config.debugMode) { - debugPrint('AppUpgrade Error: $error'); - } - - for (final callback in _errorCallbacks) { - callback(error); - } - } - - /// 通知点击回调 - Future _onNotificationClick(NotificationResponse response) async { - if (response.payload != null && response.payload!.startsWith('download_complete:')) { - final filePath = response.payload!.split(':').last; - await installApkSmart(filePath); - } - } - - /// 根据下载任务状态更新通知 - void _updateNotificationForTask(DownloadTask task) async { - final progress = (task.progress * 100).toInt(); - final appInfo = await _cacheManager.get>('app_info', strategy: CacheStrategy.cacheFirst); - final appName = appInfo?['appName'] ?? '应用'; - - switch (task.status) { - case DownloadStatus.downloading: - _notificationHelper.showDownloadProgressNotification( - progress: progress, - title: '$appName 下载中...', - body: '${(task.progress * 100).toStringAsFixed(1)}%', - ); - break; - case DownloadStatus.completed: - _notificationHelper.showDownloadCompleteNotification( - title: '$appName 下载完成', - body: '点击安装新版本', - filePath: task.savePath, - ); - break; - case DownloadStatus.failed: - _notificationHelper.showDownloadFailedNotification( - title: '$appName 下载失败', - body: task.errorMessage ?? '请检查网络后重试', - ); - break; - case DownloadStatus.cancelled: - _notificationHelper.cancelNotification(); - break; - default: - break; - } - } - - /// 获取缓存统计 - Future> getCacheStats() { - return _cacheManager.getCacheStats(); - } - - /// 清空缓存 - Future clearCache() { - return _cacheManager.clear(); - } - - /// 获取网络状态 - NetworkStatus? get networkStatus => _networkMonitor.currentStatus; - - /// 刷新网络状态 - Future refreshNetworkStatus() async { - await _networkMonitor.refreshNetworkStatus(); - } - - /// 获取当前配置 - UpgradeConfig get config => _config; - - /// 获取版本比较器 - VersionComparator get versionComparator => _versionComparator; - - /// 释放资源(优化内存) - void dispose() { - // 停止自动检查 - _stopAutoCheck(); - - // 取消所有订阅 - for (final subscription in _subscriptions) { - subscription.cancel(); - } - _subscriptions.clear(); - - // 清理回调 - _upgradeCallbacks.clear(); - _downloadCallbacks.clear(); - _errorCallbacks.clear(); - - // 释放组件资源 - _downloadManager.dispose(); - _networkMonitor.dispose(); - _cacheManager.dispose(); - - // 清理弱引用 - _weakRefs.clear(); - - // 清空当前状态 - _currentUpgradeInfo = null; - _currentDownloadTaskId = null; - } -} diff --git a/lib/app_upgrade_plugin_method_channel.dart b/lib/app_upgrade_plugin_method_channel.dart index 9b692f8..0b20169 100644 --- a/lib/app_upgrade_plugin_method_channel.dart +++ b/lib/app_upgrade_plugin_method_channel.dart @@ -167,20 +167,8 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform { debugPrint('响应数据: $responseData'); - // TODO 解析更新信息 - // final upgradeInfo = UpgradeInfo.fromJson(responseData); - final upgradeInfo = UpgradeInfo.fromJson({ - "hasUpdate": true, - "isForceUpdate": true, - "versionCode": "101", // Android buildNumber, 必须是数字字符串 - "versionName": "1.0.1", // 显示的版本名 - "updateContent": "1. 修复了xxx Bug。\n2. 优化了用户体验。", - "downloadUrl": - "https://dpc-job-oss.23544.com/infra-app/making_school_asignment_app/1.0.5/1/app-release.apk", // APK 下载地址 - "appStoreUrl": "https://itunes.apple.com/app/id123456", // iOS App Store 地址 - "apkSize": 20971520, // APK 文件大小 (单位: byte) - "apkMd5": "b10a8db164e0754105b7a99be72e3fe5" // APK 的 MD5 (可选,用于校验) - }); + // 解析更新信息 + final upgradeInfo = UpgradeInfo.fromJson(responseData); // 比较版本 if (_compareVersion(upgradeInfo.versionCode, currentBuildNumber) > 0) { diff --git a/lib/app_upgrade_simple.dart b/lib/app_upgrade_simple.dart index 49a65d6..bb06e89 100644 --- a/lib/app_upgrade_simple.dart +++ b/lib/app_upgrade_simple.dart @@ -73,25 +73,26 @@ class AppUpgradeSimple { } catch (e) { debugPrint('检查更新失败: $e'); - String errorMessage = '检查更新失败'; + final errorString = e.toString(); + final String errorMessage; - if (e.toString().contains('无网络连接')) { + if (errorString.contains('无网络连接')) { errorMessage = '无网络连接,请检查网络设置'; - } else if (e.toString().contains('Failed host lookup')) { + } else if (errorString.contains('Failed host lookup')) { errorMessage = '无法连接到服务器,请检查网络或稍后重试'; - } else if (e.toString().contains('Connection refused')) { + } else if (errorString.contains('Connection refused')) { errorMessage = '服务器拒绝连接,请稍后重试'; - } else if (e.toString().contains('timeout')) { + } else if (errorString.contains('timeout')) { errorMessage = '连接超时,请检查网络'; } else { - errorMessage = '检查更新失败: ${e.toString().split(':').first}'; + errorMessage = '检查更新失败: ${errorString.split(':').first}'; } if (context.mounted) { _showToast(errorMessage); // 如果是网络问题,显示网络诊断建议 - if (e.toString().contains('Failed host lookup') || e.toString().contains('无网络连接')) { + if (errorString.contains('Failed host lookup') || errorString.contains('无网络连接')) { debugPrint('建议: 请检查网络连接或尝试使用网络诊断功能'); } } @@ -99,26 +100,6 @@ class AppUpgradeSimple { } } - /// 在无法显示对话框时的后备逻辑:显示Toast并尝试后台下载/安装 - Future _showToastAndDownloadInBackground({ - required BuildContext context, - required UpgradeInfo info, - required bool autoDownload, - required bool autoInstall, - }) async { - if (info.isForceUpdate) { - _showToast('有重要更新,但无法显示对话框。请在 MaterialApp 环境内重试。'); - } else { - _showToast('发现新版本: ${info.versionName}'); - if (autoDownload && Platform.isAndroid && info.downloadUrl != null) { - final filePath = await _plugin.downloadApk(info.downloadUrl!, onProgress: (_) {}); - if (filePath != null && autoInstall) { - await _installApkHeadless(context, filePath); - } - } - } - } - /// 静默检查更新(不显示无更新提示) Future checkUpdateSilent({ required String url, @@ -159,37 +140,6 @@ class AppUpgradeSimple { ); } - /// 显示备用升级对话框(当MaterialApp环境不可用时) - void _showFallbackUpgradeDialog({ - required BuildContext context, - required UpgradeInfo info, - required bool autoDownload, - required bool autoInstall, - VoidCallback? onComplete, - }) { - showGeneralDialog( - context: context, - barrierDismissible: !info.isForceUpdate, - barrierLabel: 'Dismiss', // Provide a non-localized label - pageBuilder: (buildContext, animation, secondaryAnimation) { - // 使用一个包装器来提供基本的文本样式和方向,这对于独立于Material的对话框是必需的 - return Directionality( - textDirection: TextDirection.ltr, - child: _FallbackUpgradeDialog( - info: info, - autoDownload: autoDownload, - autoInstall: autoInstall, - onComplete: onComplete, - showToast: (message) => _showToast(message), - ), - ); - }, - ).then((_) { - // The dialog has been dismissed. - onComplete?.call(); - }); - } - /// 显示Toast提示 void _showToast(String message) { Fluttertoast.showToast(msg: message); @@ -206,20 +156,6 @@ class AppUpgradeSimple { return false; } } - - /// 共享的安装逻辑 (无UI) - Future _installApkHeadless(BuildContext context, String filePath) async { - if (!Platform.isAndroid) return; - final hasPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context); - if (!hasPermission) { - _showToast('未授予安装权限,无法完成更新'); - return; - } - final success = await _plugin.installApk(filePath); - if (!success) { - _showToast('安装失败,请手动安装'); - } - } } /// 共享的升级对话框内容构建器 @@ -238,7 +174,7 @@ class _UpgradeDialogContent extends StatelessWidget { @override Widget build(BuildContext context) { - final List changeItems = + final changeItems = info.updateContent.split(RegExp(r'\r?\n')).map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); return Column( @@ -392,136 +328,6 @@ mixin _UpgradeDialogLogic on State { } } - Future _showInstallMethodChooser() async { - final bool hasMarkets = info.appMarkets != null && info.appMarkets!.isNotEmpty; - final bool hasDownload = info.downloadUrl != null; - - if (!mounted) return; - - // 始终提供“应用市场”入口;无具体列表则用通用 market:// 链接 - final choice = await showModalBottomSheet( - context: context, - isScrollControlled: false, - useRootNavigator: true, - // 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)), - 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 - if (hasDownload) - 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 - 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') { - if (hasMarkets) { - await MarketSelectionDialog.show( - context, - markets: info.appMarkets!, - onSelected: (market) async { - if (market.url != null && market.url!.isNotEmpty) { - _plugin.goToAppStore(market.url!); - } else { - final appInfo = await _plugin.getAppInfo(); - final pkg = appInfo['packageName'] ?? ''; - if (pkg.isNotEmpty) { - _plugin.goToAppStore('market://details?id=$pkg'); - } - } - }, - ); - } else { - final appInfo = await _plugin.getAppInfo(); - final pkg = appInfo['packageName'] ?? ''; - if (pkg.isNotEmpty) { - _plugin.goToAppStore('market://details?id=$pkg'); - } - } - if (!mounted) return; - Navigator.of(context).pop(); - onComplete?.call(); - return; - } - - if (choice == 'download' && hasDownload && !_isDownloading) { - await _startDownloadAndInstall(); - } - } - void _handleAction() { if (Platform.isAndroid) { _handleAndroidAction(); @@ -550,14 +356,10 @@ mixin _UpgradeDialogLogic on State { Future _handleAndroidAction() async { // On Android, we always assume a market option is available, // because we can fall back to a generic market:// intent. - const bool hasMarketOption = true; final bool hasDownloadOption = info.downloadUrl != null; - // This case is unlikely on Android, but kept for robustness. - if (!hasMarketOption && !hasDownloadOption) { - showToast('No update method available.'); - return; - } + // 在Android上,我们总是假设市场选项可用,因为可以回退到通用的 market:// intent + // 因此这里不需要检查无更新方法的情况 // If a download option is not available, going to the market is the only choice. if (!hasDownloadOption) { @@ -587,7 +389,6 @@ mixin _UpgradeDialogLogic on State { final pkg = appInfo['packageName'] ?? ''; if (pkg.isNotEmpty) { _plugin.goToAppStore('market://details?id=$pkg'); - // _plugin.goToAppStore('market://details?id=$pkg'); } else { showToast('Could not determine app package name.'); } @@ -596,8 +397,8 @@ mixin _UpgradeDialogLogic on State { /// Pops the current dialog and then performs the market action. Future _handleMarketAction() async { - // Pop the upgrade dialog before proceeding. - if (Navigator.canPop(context)) { + // 对于强制更新,不关闭对话框 + if (!info.isForceUpdate && Navigator.canPop(context)) { Navigator.of(context).pop(); } if (!mounted) return; @@ -606,8 +407,6 @@ mixin _UpgradeDialogLogic on State { } Future _showDownloadChoiceSheet() async { - final bool hasDownload = info.downloadUrl != null; - if (!mounted) return; final choice = await showModalBottomSheet( @@ -662,13 +461,12 @@ mixin _UpgradeDialogLogic on State { ), // Option 2: Direct Download - if (hasDownload) - ListTile( - leading: const Icon(Icons.download_for_offline_outlined), - title: const Text('直接下载安装包'), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - onTap: () => Navigator.of(ctx).pop('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), @@ -699,12 +497,17 @@ mixin _UpgradeDialogLogic on State { ); if (choice == 'market') { - await _performMarketAction(); - onComplete?.call(); + // 对于强制更新,不调用 onComplete,保持对话框打开状态 + if (!info.isForceUpdate) { + await _performMarketAction(); + onComplete?.call(); + } else { + await _performMarketAction(); + } return; } - if (choice == 'download' && hasDownload && !_isDownloading) { + if (choice == 'download' && !_isDownloading) { await _startDownloadAndInstall(); } } @@ -744,8 +547,6 @@ class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _Upgrad @override Widget build(BuildContext context) { - final theme = Theme.of(context); - return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -756,7 +557,7 @@ class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _Upgrad children: [ Icon( Icons.system_update, - color: theme.colorScheme.primary, + color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), const Text('发现新版本'), @@ -772,14 +573,13 @@ class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _Upgrad actions: _isDownloading ? [] : [ - if (!widget.info.isForceUpdate) - TextButton( - onPressed: () { - Navigator.of(context).pop(); - widget.onComplete?.call(); - }, - child: const Text('稍后更新'), - ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + widget.onComplete?.call(); + }, + child: const Text('稍后更新'), + ), ElevatedButton( onPressed: _handleAction, child: Text(Platform.isAndroid ? '立即更新' : '前往更新'), @@ -799,78 +599,20 @@ class _ForceUpgradeDialog extends StatefulWidget { State<_ForceUpgradeDialog> createState() => _ForceUpgradeDialogState(); } -class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> { - final _plugin = AppUpgradePlugin(); - bool _isDownloading = false; - double _downloadProgress = 0; - String _statusText = ''; - +class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> with _UpgradeDialogLogic { @override - void initState() { - super.initState(); - // 强制更新也需用户交互后再开始下载 - } - - Future _startDownload() async { - // 下载前申请权限 (Just-in-Time) - if (Platform.isAndroid) { - final hasStorage = await PermissionHelper.checkAndRequestStoragePermission(context: context); - if (!hasStorage) { - setState(() { - _statusText = '缺少存储权限,无法下载'; - }); - return; - } - // 通知权限仅为辅助 - await PermissionHelper.checkAndRequestNotificationPermission(context: context); - } - - if (widget.info.downloadUrl == null) { - setState(() { - _statusText = '下载地址无效'; - }); - return; - } - - setState(() { - _isDownloading = true; - _statusText = '准备下载...'; - }); - - final filePath = await _plugin.downloadApk( - widget.info.downloadUrl!, - onProgress: (p) { - if (!mounted) return; - setState(() { - _downloadProgress = p.progress; - _statusText = '下载中 ${p.percentage}%'; - }); - }, - ); - - if (filePath != null) { - await _installApk(context, filePath); - } - } - - // 进度已在 downloadApk 的回调中更新 - - Future _installApk(BuildContext context, String filePath) async { - final hasPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context); - if (!hasPermission) { - if (context.mounted) { - setState(() { - _statusText = '未授予安装权限,请手动授权后重试'; - }); - } - return; - } - await _plugin.installApk(filePath); - } + UpgradeInfo get info => widget.info; + @override + void Function(String) get showToast => (message) => AppUpgradeSimple.instance._showToast(message); + @override + VoidCallback? get onComplete => null; + @override + bool get autoDownload => false; + @override + bool get autoInstall => true; @override Widget build(BuildContext context) { - final theme = Theme.of(context); return WillPopScope( onWillPop: () async => false, // 强制更新,不允许返回 child: AlertDialog( @@ -883,7 +625,7 @@ class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> { children: [ Icon( Icons.system_update, - color: theme.colorScheme.primary, + color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), const Text('发现新版本 (强制)'), @@ -900,253 +642,11 @@ class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> { ? [] : [ ElevatedButton( - onPressed: () async { - // 强制更新下也先让用户选择安装方式 - final hasMarkets = widget.info.appMarkets != null && widget.info.appMarkets!.isNotEmpty; - final hasDownload = widget.info.downloadUrl != null; - - if (hasMarkets && hasDownload) { - final choice = await showModalBottomSheet( - context: context, - isScrollControlled: false, - useRootNavigator: true, - // 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)), - 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 - if (hasDownload) - 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 - 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') { - Navigator.of(context).pop(); - MarketSelectionDialog.show( - context, - markets: widget.info.appMarkets!, - onSelected: (market) { - _plugin.goToAppStore(market.url ?? market.packageName ?? ''); - }, - ); - return; - } - if (choice == 'download') { - await _startDownload(); - return; - } - return; - } - - if (hasMarkets) { - Navigator.of(context).pop(); - MarketSelectionDialog.show( - context, - markets: widget.info.appMarkets!, - onSelected: (market) { - _plugin.goToAppStore(market.url ?? market.packageName ?? ''); - }, - ); - return; - } - - if (hasDownload) { - await _startDownload(); - return; - } - }, - child: const Text('立即更新'), + onPressed: _handleAction, + child: Text(Platform.isAndroid ? '立即更新' : '前往更新'), ), ], ), ); } } - -/// 当 [MaterialApp] 不可用时,提供一个基础的回退升级对话框 -class _FallbackUpgradeDialog extends StatefulWidget { - final UpgradeInfo info; - final bool autoDownload; - final bool autoInstall; - final VoidCallback? onComplete; - final void Function(String) showToast; - - const _FallbackUpgradeDialog({ - required this.info, - required this.autoDownload, - required this.autoInstall, - this.onComplete, - required this.showToast, - }); - - @override - State<_FallbackUpgradeDialog> createState() => _FallbackUpgradeDialogState(); -} - -class _FallbackUpgradeDialogState extends State<_FallbackUpgradeDialog> with _UpgradeDialogLogic { - @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 - Widget build(BuildContext context) { - final title = '发现新版本${widget.info.isForceUpdate ? " (强制)" : ""}'; - - final body = Center( - child: Material( - type: MaterialType.transparency, - child: Container( - margin: const EdgeInsets.all(24.0), - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16.0), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - title, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - DefaultTextStyle( - style: const TextStyle(color: Colors.black87, fontSize: 14), - child: _UpgradeDialogContent( - info: widget.info, - isDownloading: _isDownloading, - downloadProgress: _downloadProgress, - statusText: _statusText, - ), - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (!widget.info.isForceUpdate && !_isDownloading) ...[ - GestureDetector( - onTap: () { - Navigator.of(context).pop(); - widget.onComplete?.call(); - }, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Text('稍后更新', style: TextStyle(color: Colors.blue)), - ), - ), - const SizedBox(width: 8), - ], - GestureDetector( - onTap: _isDownloading ? null : _handleAction, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: _isDownloading ? Colors.grey : Colors.blue, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - _isDownloading - ? '下载中...' - : Platform.isAndroid - ? '立即更新' - : '前往更新', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ], - ), - ], - ), - ), - ), - ); - - return widget.info.isForceUpdate ? WillPopScope(onWillPop: () async => false, child: body) : body; - } -} diff --git a/lib/core/cache_manager.dart b/lib/core/cache_manager.dart deleted file mode 100644 index 2a5925b..0000000 --- a/lib/core/cache_manager.dart +++ /dev/null @@ -1,495 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; // Added for WidgetsBinding -import 'package:path_provider/path_provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -/// 缓存条目 -class CacheEntry { - final String key; - final T data; - final DateTime createdAt; - final DateTime? expiresAt; - final Map? metadata; - - CacheEntry({required this.key, required this.data, required this.createdAt, this.expiresAt, this.metadata}); - - bool get isExpired { - if (expiresAt == null) return false; - return DateTime.now().isAfter(expiresAt!); - } - - Map toJson() => { - 'key': key, - 'data': data is Map || data is List ? data : data.toString(), - 'createdAt': createdAt.toIso8601String(), - 'expiresAt': expiresAt?.toIso8601String(), - 'metadata': metadata, - }; - - factory CacheEntry.fromJson(Map json) { - return CacheEntry( - key: json['key'], - data: json['data'] as T, - createdAt: DateTime.parse(json['createdAt']), - expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt']) : null, - metadata: json['metadata'], - ); - } -} - -/// 缓存策略 -enum CacheStrategy { - /// 优先使用缓存,缓存过期后才请求网络 - cacheFirst, - - /// 优先使用网络,网络失败时使用缓存 - networkFirst, - - /// 只使用缓存 - cacheOnly, - - /// 只使用网络 - networkOnly, - - /// 同时请求网络和缓存,返回最快的结果 - fastest, -} - -/// 智能缓存管理器 -class CacheManager { - static CacheManager? _instance; - static CacheManager get instance { - _instance ??= CacheManager._(); - return _instance!; - } - - CacheManager._() { - _scheduleInit(); - } - - late SharedPreferences _prefs; - late Directory _cacheDir; - final Map _memoryCache = {}; - final Map _autoCleanTimers = {}; - - static const String _cachePrefix = 'app_upgrade_cache_'; - static const String _cacheMetaKey = 'cache_metadata'; - static const int _maxMemoryCacheSize = 50; // 最大内存缓存条目数 - static const int _maxDiskCacheSize = 100 * 1024 * 1024; // 100MB - - /// 延迟初始化 - void _scheduleInit() { - // 如果绑定已经初始化,直接执行 - _init().catchError((e) { - debugPrint('Failed to initialize CacheManager: $e'); - }); - } - - /// 初始化 - Future _init() async { - _prefs = await SharedPreferences.getInstance(); - _cacheDir = await _getCacheDirectory(); - _startAutoClean(); - await _cleanExpiredCache(); - } - - /// 获取缓存目录 - Future _getCacheDirectory() async { - final tempDir = await getTemporaryDirectory(); - final cacheDir = Directory('${tempDir.path}/app_upgrade_cache'); - if (!await cacheDir.exists()) { - await cacheDir.create(recursive: true); - } - return cacheDir; - } - - /// 保存到缓存 - Future save({ - required String key, - required T data, - Duration? expiration, - CacheStrategy strategy = CacheStrategy.cacheFirst, - Map? metadata, - }) async { - final fullKey = '$_cachePrefix$key'; - final expiresAt = expiration != null ? DateTime.now().add(expiration) : null; - - final entry = CacheEntry( - key: fullKey, - data: data, - createdAt: DateTime.now(), - expiresAt: expiresAt, - metadata: metadata, - ); - - // 保存到内存缓存 - _saveToMemory(fullKey, entry); - - // 保存到磁盘缓存 - await _saveToDisk(fullKey, entry); - - // 更新缓存元数据 - await _updateCacheMetadata(fullKey, entry); - } - - /// 从缓存获取 - Future get( - String key, { - CacheStrategy strategy = CacheStrategy.cacheFirst, - Future Function()? networkFetcher, - }) async { - final fullKey = '$_cachePrefix$key'; - - switch (strategy) { - case CacheStrategy.cacheFirst: - return await _getCacheFirst(fullKey, networkFetcher); - case CacheStrategy.networkFirst: - return await _getNetworkFirst(fullKey, networkFetcher); - case CacheStrategy.cacheOnly: - return await _getCacheOnly(fullKey); - case CacheStrategy.networkOnly: - return await _getNetworkOnly(networkFetcher); - case CacheStrategy.fastest: - return await _getFastest(fullKey, networkFetcher); - } - } - - /// 缓存优先策略 - Future _getCacheFirst(String key, Future Function()? fetcher) async { - // 先从内存缓存获取 - final memoryEntry = _memoryCache[key]; - if (memoryEntry != null && !memoryEntry.isExpired) { - return memoryEntry.data as T?; - } - - // 从磁盘缓存获取 - final diskEntry = await _getFromDisk(key); - if (diskEntry != null && !diskEntry.isExpired) { - _saveToMemory(key, diskEntry); - return diskEntry.data; - } - - // 缓存无效,从网络获取 - if (fetcher != null) { - try { - final data = await fetcher(); - if (data != null) { - await save(key: key.replaceFirst(_cachePrefix, ''), data: data); - } - return data; - } catch (e) { - // 网络失败,返回过期的缓存 - if (diskEntry != null) { - return diskEntry.data; - } - rethrow; - } - } - - return null; - } - - /// 网络优先策略 - Future _getNetworkFirst(String key, Future Function()? fetcher) async { - if (fetcher != null) { - try { - final data = await fetcher(); - if (data != null) { - await save(key: key.replaceFirst(_cachePrefix, ''), data: data); - } - return data; - } catch (e) { - // 网络失败,使用缓存 - final entry = await _getFromDisk(key) ?? _memoryCache[key] as CacheEntry?; - if (entry != null) { - return entry.data; - } - rethrow; - } - } - - return null; - } - - /// 仅缓存策略 - Future _getCacheOnly(String key) async { - final memoryEntry = _memoryCache[key]; - if (memoryEntry != null) { - return memoryEntry.data as T?; - } - - final diskEntry = await _getFromDisk(key); - if (diskEntry != null) { - _saveToMemory(key, diskEntry); - return diskEntry.data; - } - - return null; - } - - /// 仅网络策略 - Future _getNetworkOnly(Future Function()? fetcher) async { - if (fetcher != null) { - return await fetcher(); - } - return null; - } - - /// 最快响应策略 - Future _getFastest(String key, Future Function()? fetcher) async { - final futures = >[]; - - // 添加缓存获取 - futures.add(_getCacheOnly(key)); - - // 添加网络获取 - if (fetcher != null) { - futures.add(fetcher()); - } - - // 返回最快的结果 - try { - final result = await Future.any(futures); - - // 如果是网络结果,保存到缓存 - if (result != null && fetcher != null) { - final cacheResult = await _getCacheOnly(key); - if (cacheResult == null || cacheResult != result) { - await save(key: key.replaceFirst(_cachePrefix, ''), data: result); - } - } - - return result; - } catch (e) { - return null; - } - } - - /// 保存到内存缓存 - void _saveToMemory(String key, CacheEntry entry) { - _memoryCache[key] = entry; - - // 限制内存缓存大小 - if (_memoryCache.length > _maxMemoryCacheSize) { - // 移除最旧的条目 - final oldestKey = - _memoryCache.entries.reduce((a, b) => a.value.createdAt.isBefore(b.value.createdAt) ? a : b).key; - _memoryCache.remove(oldestKey); - } - } - - /// 保存到磁盘缓存 - Future _saveToDisk(String key, CacheEntry entry) async { - try { - final file = File('${_cacheDir.path}/${_encodeKey(key)}.json'); - final json = jsonEncode(entry.toJson()); - await file.writeAsString(json); - - // 检查磁盘缓存大小 - await _checkDiskCacheSize(); - } catch (e) { - debugPrint('Failed to save to disk cache: $e'); - } - } - - /// 从磁盘缓存获取 - Future?> _getFromDisk(String key) async { - try { - final file = File('${_cacheDir.path}/${_encodeKey(key)}.json'); - if (!await file.exists()) return null; - - final json = await file.readAsString(); - final data = jsonDecode(json); - return CacheEntry.fromJson(data); - } catch (e) { - debugPrint('Failed to get from disk cache: $e'); - return null; - } - } - - /// 更新缓存元数据 - Future _updateCacheMetadata(String key, CacheEntry entry) async { - final metadata = _prefs.getString(_cacheMetaKey); - final metaMap = metadata != null ? jsonDecode(metadata) : {}; - - metaMap[key] = { - 'createdAt': entry.createdAt.toIso8601String(), - 'expiresAt': entry.expiresAt?.toIso8601String(), - 'size': entry.toJson().toString().length, - }; - - await _prefs.setString(_cacheMetaKey, jsonEncode(metaMap)); - } - - /// 删除缓存 - Future delete(String key) async { - final fullKey = '$_cachePrefix$key'; - - // 从内存缓存删除 - _memoryCache.remove(fullKey); - - // 从磁盘缓存删除 - final file = File('${_cacheDir.path}/${_encodeKey(fullKey)}.json'); - if (await file.exists()) { - await file.delete(); - } - - // 更新元数据 - final metadata = _prefs.getString(_cacheMetaKey); - if (metadata != null) { - final metaMap = jsonDecode(metadata); - metaMap.remove(fullKey); - await _prefs.setString(_cacheMetaKey, jsonEncode(metaMap)); - } - } - - /// 清空所有缓存 - Future clear() async { - // 清空内存缓存 - _memoryCache.clear(); - - // 清空磁盘缓存 - if (await _cacheDir.exists()) { - await _cacheDir.delete(recursive: true); - await _cacheDir.create(); - } - - // 清空元数据 - await _prefs.remove(_cacheMetaKey); - } - - /// 清理过期缓存 - Future _cleanExpiredCache() async { - // 清理内存缓存 - _memoryCache.removeWhere((key, entry) => entry.isExpired); - - // 清理磁盘缓存 - final metadata = _prefs.getString(_cacheMetaKey); - if (metadata != null) { - final metaMap = jsonDecode(metadata) as Map; - final keysToRemove = []; - - for (final entry in metaMap.entries) { - final expiresAt = entry.value['expiresAt']; - if (expiresAt != null) { - final expireTime = DateTime.parse(expiresAt); - if (DateTime.now().isAfter(expireTime)) { - keysToRemove.add(entry.key); - } - } - } - - for (final key in keysToRemove) { - await delete(key.replaceFirst(_cachePrefix, '')); - } - } - } - - /// 检查磁盘缓存大小 - Future _checkDiskCacheSize() async { - int totalSize = 0; - final files = await _cacheDir.list().toList(); - - for (final file in files) { - if (file is File) { - totalSize += await file.length(); - } - } - - if (totalSize > _maxDiskCacheSize) { - // 删除最旧的文件 - final fileStats = >[]; - for (final file in files) { - if (file is File) { - fileStats.add(MapEntry(file, await file.stat())); - } - } - - fileStats.sort((a, b) => a.value.accessed.compareTo(b.value.accessed)); - - // 删除文件直到大小合适 - for (final entry in fileStats) { - final fileSize = await entry.key.length(); - await entry.key.delete(); - totalSize -= fileSize; - if (totalSize <= (_maxDiskCacheSize * 0.8).toInt()) break; - } - } - } - - /// 编码缓存键 - String _encodeKey(String key) { - return base64Encode(utf8.encode(key)).replaceAll('/', '_'); - } - - /// 启动自动清理 - void _startAutoClean() { - // 每小时清理一次过期缓存 - _autoCleanTimers['expired'] = Timer.periodic(const Duration(hours: 1), (_) => _cleanExpiredCache()); - } - - /// 获取缓存统计信息 - Future> getCacheStats() async { - int memoryCount = _memoryCache.length; - int memorySize = 0; - - for (final entry in _memoryCache.values) { - memorySize += entry.toJson().toString().length; - } - - int diskCount = 0; - int diskSize = 0; - - final files = await _cacheDir.list().toList(); - for (final file in files) { - if (file is File) { - diskCount++; - diskSize += await file.length(); - } - } - - return { - 'memoryCache': {'count': memoryCount, 'size': memorySize, 'sizeFormatted': _formatBytes(memorySize)}, - 'diskCache': {'count': diskCount, 'size': diskSize, 'sizeFormatted': _formatBytes(diskSize)}, - 'total': { - 'count': memoryCount + diskCount, - 'size': memorySize + diskSize, - 'sizeFormatted': _formatBytes(memorySize + diskSize), - }, - }; - } - - /// 格式化字节数 - String _formatBytes(int bytes) { - if (bytes < 1024) return '$bytes B'; - if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; - if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; - return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; - } - - /// 预加载缓存 - Future preload(List keys) async { - for (final key in keys) { - final fullKey = '$_cachePrefix$key'; - if (!_memoryCache.containsKey(fullKey)) { - final entry = await _getFromDisk(fullKey); - if (entry != null && !entry.isExpired) { - _saveToMemory(fullKey, entry); - } - } - } - } - - /// 释放资源 - void dispose() { - for (final timer in _autoCleanTimers.values) { - timer.cancel(); - } - _autoCleanTimers.clear(); - _memoryCache.clear(); - } -} diff --git a/lib/core/download_manager.dart b/lib/core/download_manager.dart deleted file mode 100644 index 37f5781..0000000 --- a/lib/core/download_manager.dart +++ /dev/null @@ -1,474 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:crypto/crypto.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:path_provider/path_provider.dart'; - -import 'upgrade_config.dart'; - -/// 下载任务状态 -enum DownloadStatus { pending, downloading, paused, completed, failed, cancelled } - -/// 下载任务信息 -class DownloadTask { - final String id; - final String url; - final String savePath; - final Map? headers; - final String? md5; - final String? sha256; - int? totalSize; - - DownloadStatus status; - int downloadedSize; - double progress; - String? errorMessage; - int retryCount; - DateTime? startTime; - DateTime? endTime; - - DownloadTask({ - required this.id, - required this.url, - required this.savePath, - this.headers, - this.md5, - this.sha256, - this.totalSize, - this.status = DownloadStatus.pending, - this.downloadedSize = 0, - this.progress = 0.0, - this.errorMessage, - this.retryCount = 0, - this.startTime, - this.endTime, - }); - - Map toJson() => { - 'id': id, - 'url': url, - 'savePath': savePath, - 'headers': headers, - 'md5': md5, - 'sha256': sha256, - 'totalSize': totalSize, - 'status': status.toString(), - 'downloadedSize': downloadedSize, - 'progress': progress, - 'errorMessage': errorMessage, - 'retryCount': retryCount, - 'startTime': startTime?.toIso8601String(), - 'endTime': endTime?.toIso8601String(), - }; -} - -/// 高性能下载管理器 -class DownloadManager { - static DownloadManager? _instance; - static DownloadManager get instance { - _instance ??= DownloadManager._(); - return _instance!; - } - - DownloadManager._() { - _initializeDio(); - } - - final Dio _dio = Dio(); - final Map _tasks = {}; - final Map _cancelTokens = {}; - final Map> _progressControllers = {}; - final UpgradeConfig _config = UpgradeConfig.instance; - - Timer? _speedCalculatorTimer; - final Map> _speedSamples = {}; - - /// 初始化Dio配置 - void _initializeDio() { - _dio.options.connectTimeout = Duration(seconds: _config.connectTimeout); - _dio.options.receiveTimeout = Duration(seconds: _config.downloadTimeout); - - if (_config.proxyUrl != null) { - (_dio.httpClientAdapter as dynamic).onHttpClientCreate = (client) { - client.findProxy = (uri) { - return 'PROXY ${_config.proxyUrl}'; - }; - return client; - }; - } - - // 添加拦截器 - _dio.interceptors.add(LogInterceptor(request: _config.debugMode, responseBody: false, error: _config.debugMode)); - - _dio.interceptors.add( - InterceptorsWrapper( - onRequest: (options, handler) { - // 添加自定义请求头 - options.headers.addAll(_config.customHeaders); - handler.next(options); - }, - onError: (error, handler) { - if (_config.debugMode) { - debugPrint('Download error: ${error.message}'); - } - handler.next(error); - }, - ), - ); - } - - /// 创建下载任务 - Future createTask({ - required String url, - String? savePath, - Map? headers, - String? md5, - String? sha256, - int? expectedSize, - String? versionName, // 新增版本名参数 - }) async { - final taskId = DateTime.now().millisecondsSinceEpoch.toString(); - - if (savePath == null) { - final dir = await _getDownloadDirectory(); - // 如果提供了版本名,则使用标准化的文件名 - final fileName = versionName != null ? 'app-upgrade-$versionName.apk' : url.split('/').last.split('?').first; - savePath = '${dir.path}/$fileName'; - } - - final task = DownloadTask( - id: taskId, - url: url, - savePath: savePath, - headers: headers, - md5: md5, - sha256: sha256, - totalSize: expectedSize, - ); - - _tasks[taskId] = task; - _progressControllers[taskId] = StreamController.broadcast(); - - return taskId; - } - - /// 开始下载 - Future startDownload(String taskId) async { - final task = _tasks[taskId]; - if (task == null) return false; - - task.status = DownloadStatus.downloading; - task.startTime = DateTime.now(); - - final cancelToken = CancelToken(); - _cancelTokens[taskId] = cancelToken; - - // 启动速度计算定时器 - _startSpeedCalculator(taskId); - - try { - // 检查是否支持断点续传 - if (_config.supportBreakpoint && await _checkBreakpointSupport(task.url)) { - await _downloadWithBreakpoint(task, cancelToken); - } else { - await _downloadNormal(task, cancelToken); - } - - // 校验文件完整性 - if (_config.verifyIntegrity && (task.md5 != null || task.sha256 != null)) { - final isValid = await _verifyFileIntegrity(task); - if (!isValid) { - throw Exception('File integrity check failed'); - } - } - - task.status = DownloadStatus.completed; - task.endTime = DateTime.now(); - task.progress = 1.0; - _notifyProgress(task); - - return true; - } catch (e) { - if (e is DioException && e.type == DioExceptionType.cancel) { - task.status = DownloadStatus.cancelled; - } else { - task.status = DownloadStatus.failed; - task.errorMessage = e.toString(); - - // 自动重试 - if (task.retryCount < _config.maxRetryCount) { - task.retryCount++; - await Future.delayed(Duration(seconds: _config.retryDelay)); - return startDownload(taskId); - } - } - - _notifyProgress(task); - return false; - } finally { - _stopSpeedCalculator(taskId); - _cancelTokens.remove(taskId); - } - } - - /// 普通下载 - Future _downloadNormal(DownloadTask task, CancelToken cancelToken) async { - final response = await _dio.download( - task.url, - task.savePath, - cancelToken: cancelToken, - options: Options(headers: task.headers), - onReceiveProgress: (received, total) { - task.downloadedSize = received; - task.totalSize ??= total; - task.progress = total > 0 ? received / total : 0.0; - _notifyProgress(task); - }, - ); - - if (response.statusCode != 200 && response.statusCode != 206) { - throw Exception('Download failed with status: ${response.statusCode}'); - } - } - - /// 断点续传下载 - Future _downloadWithBreakpoint(DownloadTask task, CancelToken cancelToken) async { - final file = File(task.savePath); - int downloadedBytes = 0; - - // 检查文件是否已存在 - if (await file.exists()) { - downloadedBytes = await file.length(); - task.downloadedSize = downloadedBytes; - } - - // 如果文件已下载完成 - if (task.totalSize != null && downloadedBytes >= task.totalSize!) { - task.progress = 1.0; - _notifyProgress(task); - return; - } - - // 设置Range头 - final headers = Map.from(task.headers ?? {}); - headers['Range'] = 'bytes=$downloadedBytes-'; - - // 创建文件流 - final raf = await file.open(mode: FileMode.append); - - try { - final response = await _dio.get( - task.url, - cancelToken: cancelToken, - options: Options(headers: headers, responseType: ResponseType.stream), - ); - - final total = int.tryParse(response.headers.value('content-length') ?? '0') ?? 0; - - task.totalSize ??= total + downloadedBytes; - - // 写入文件 - await for (final chunk in response.data!.stream) { - await raf.writeFrom(chunk); - task.downloadedSize += chunk.length; - task.progress = task.totalSize! > 0 ? task.downloadedSize / task.totalSize! : 0.0; - _notifyProgress(task); - } - } finally { - await raf.close(); - } - } - - /// 检查是否支持断点续传 - Future _checkBreakpointSupport(String url) async { - try { - final response = await _dio.head(url); - final acceptRanges = response.headers.value('accept-ranges'); - return acceptRanges == 'bytes'; - } catch (e) { - return false; - } - } - - /// 暂停下载 - void pauseDownload(String taskId) { - final cancelToken = _cancelTokens[taskId]; - if (cancelToken != null && !cancelToken.isCancelled) { - cancelToken.cancel('User paused'); - final task = _tasks[taskId]; - if (task != null) { - task.status = DownloadStatus.paused; - _notifyProgress(task); - } - } - } - - /// 恢复下载 - Future resumeDownload(String taskId) async { - final task = _tasks[taskId]; - if (task != null && task.status == DownloadStatus.paused) { - return startDownload(taskId); - } - return false; - } - - /// 取消下载 - void cancelDownload(String taskId) { - final cancelToken = _cancelTokens[taskId]; - if (cancelToken != null && !cancelToken.isCancelled) { - cancelToken.cancel('User cancelled'); - } - - final task = _tasks[taskId]; - if (task != null) { - task.status = DownloadStatus.cancelled; - _notifyProgress(task); - - // 删除未完成的文件 - final file = File(task.savePath); - if (file.existsSync()) { - file.deleteSync(); - } - } - - _cleanupTask(taskId); - } - - /// 重试下载 - Future retryDownload(String taskId) async { - final task = _tasks[taskId]; - if (task != null && task.status == DownloadStatus.failed) { - task.retryCount = 0; - task.errorMessage = null; - return startDownload(taskId); - } - return false; - } - - /// 获取任务信息 - DownloadTask? getTask(String taskId) => _tasks[taskId]; - - /// 获取所有任务 - List getAllTasks() => _tasks.values.toList(); - - /// 获取任务进度流 - Stream? getProgressStream(String taskId) { - return _progressControllers[taskId]?.stream; - } - - /// 清理任务 - void _cleanupTask(String taskId) { - _tasks.remove(taskId); - _cancelTokens.remove(taskId); - _progressControllers[taskId]?.close(); - _progressControllers.remove(taskId); - _speedSamples.remove(taskId); - } - - /// 通知进度更新 - void _notifyProgress(DownloadTask task) { - _progressControllers[task.id]?.add(task); - } - - /// 启动速度计算器 - void _startSpeedCalculator(String taskId) { - _speedSamples[taskId] = []; - _speedCalculatorTimer?.cancel(); - _speedCalculatorTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - final task = _tasks[taskId]; - if (task != null && task.status == DownloadStatus.downloading) { - final samples = _speedSamples[taskId]!; - samples.add(task.downloadedSize); - - // 保留最近10个采样点 - if (samples.length > 10) { - samples.removeAt(0); - } - - // 计算平均速度 - if (samples.length >= 2) { - final speed = samples.last - samples.first; - final timeSpan = samples.length - 1; - final avgSpeed = speed / timeSpan; - - // 可以在这里发送速度信息 - if (_config.debugMode) { - debugPrint('Download speed: ${_formatBytes(avgSpeed.toInt())}/s'); - } - } - } - }); - } - - /// 停止速度计算器 - void _stopSpeedCalculator(String taskId) { - _speedSamples.remove(taskId); - if (_speedSamples.isEmpty) { - _speedCalculatorTimer?.cancel(); - _speedCalculatorTimer = null; - } - } - - /// 验证文件完整性 - Future _verifyFileIntegrity(DownloadTask task) async { - return compute(verifyFileInIsolate, {'filePath': task.savePath, 'md5': task.md5, 'sha256': task.sha256}); - } - - /// 在Isolate中验证文件 - static Future verifyFileInIsolate(Map params) async { - final filePath = params['filePath'] as String; - final expectedMd5 = params['md5'] as String?; - final expectedSha256 = params['sha256'] as String?; - - final file = File(filePath); - if (!await file.exists()) return false; - - final bytes = await file.readAsBytes(); - - if (expectedMd5 != null) { - final md5Hash = md5.convert(bytes).toString(); - if (md5Hash != expectedMd5) return false; - } - - if (expectedSha256 != null) { - final sha256Hash = sha256.convert(bytes).toString(); - if (sha256Hash != expectedSha256) return false; - } - - return true; - } - - /// 获取下载目录 - Future _getDownloadDirectory() async { - if (Platform.isAndroid) { - return (await getExternalStorageDirectory())!; - } else { - return getApplicationDocumentsDirectory(); - } - } - - /// 格式化字节数 - String _formatBytes(int bytes) { - if (bytes < 1024) return '$bytes B'; - if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; - if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; - return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; - } - - /// 清理所有任务 - void clearAllTasks() { - for (final taskId in _tasks.keys.toList()) { - cancelDownload(taskId); - } - _tasks.clear(); - } - - /// 释放资源 - void dispose() { - clearAllTasks(); - _speedCalculatorTimer?.cancel(); - _dio.close(); - } -} diff --git a/lib/core/network_monitor.dart b/lib/core/network_monitor.dart deleted file mode 100644 index 6c01857..0000000 --- a/lib/core/network_monitor.dart +++ /dev/null @@ -1,516 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; // Added for WidgetsBinding - -/// 网络类型 -enum NetworkType { none, mobile, wifi, ethernet, bluetooth, vpn, other } - -/// 网络质量 -enum NetworkQuality { - unknown, - poor, // 差 (<100KB/s) - moderate, // 中等 (100KB/s - 500KB/s) - good, // 良好 (500KB/s - 2MB/s) - excellent, // 优秀 (>2MB/s) -} - -/// 网络状态信息 -class NetworkStatus { - final NetworkType type; - final NetworkQuality quality; - final bool isConnected; - final bool isMetered; - final double? downloadSpeed; - final double? uploadSpeed; - final int? ping; - final DateTime timestamp; - - NetworkStatus({ - required this.type, - required this.quality, - required this.isConnected, - required this.isMetered, - this.downloadSpeed, - this.uploadSpeed, - this.ping, - DateTime? timestamp, - }) : timestamp = timestamp ?? DateTime.now(); - - /// 是否适合下载大文件 - bool get isSuitableForLargeDownload { - return isConnected && (type == NetworkType.wifi || type == NetworkType.ethernet) && quality != NetworkQuality.poor; - } - - /// 是否应该限速 - bool get shouldLimitSpeed { - return type == NetworkType.mobile || quality == NetworkQuality.poor; - } - - Map toJson() => { - 'type': type.toString(), - 'quality': quality.toString(), - 'isConnected': isConnected, - 'isMetered': isMetered, - 'downloadSpeed': downloadSpeed, - 'uploadSpeed': uploadSpeed, - 'ping': ping, - 'timestamp': timestamp.toIso8601String(), - }; -} - -/// 网络监测器 -class NetworkMonitor { - static NetworkMonitor? _instance; - static NetworkMonitor get instance { - _instance ??= NetworkMonitor._(); - return _instance!; - } - - NetworkMonitor._() { - _init().catchError((e) { - debugPrint('NetworkMonitor initialization failed: $e'); - }); - } - - final Connectivity _connectivity = Connectivity(); - NetworkStatus? _currentStatus; - - final _statusController = StreamController.broadcast(); - Stream get statusStream => _statusController.stream; - - StreamSubscription>? _connectivitySubscription; - Timer? _qualityCheckTimer; - - final List _speedSamples = []; - static const int _maxSamples = 10; - - /// 初始化 - Future _init() async { - debugPrint('NetworkMonitor: Starting initialization'); - - // 立即开始网络监听,不等待Flutter绑定 - _startMonitoring(); - - // 检查初始网络状态 - await _checkInitialStatus(); - - // 启动质量检查 - _startQualityCheck(); - - debugPrint('NetworkMonitor: Initialization completed'); - } - - /// 检查初始状态 - Future _checkInitialStatus() async { - try { - debugPrint('NetworkMonitor: Checking initial connectivity...'); - final result = await _connectivity.checkConnectivity(); - debugPrint('NetworkMonitor: Initial connectivity result: $result'); - - // 强制触发网络状态更新 - await _updateStatus(result); - } catch (e) { - debugPrint('NetworkMonitor: Failed to check initial network status: $e'); - // 如果检查失败,设置默认状态 - await _updateStatus(ConnectivityResult.none); - } - } - - /// 开始监听网络变化 - void _startMonitoring() { - debugPrint('NetworkMonitor: Starting connectivity monitoring...'); - _connectivitySubscription?.cancel(); // 确保之前的监听器被取消 - - _connectivitySubscription = _connectivity.onConnectivityChanged.listen( - (List results) async { - debugPrint('NetworkMonitor: Connectivity changed: $results'); - await _updateStatus(results); - }, - onError: (error) { - debugPrint('NetworkMonitor: Connectivity monitoring error: $error'); - }, - cancelOnError: false, - ); - } - - /// 更新网络状态 - Future _updateStatus(dynamic results) async { - List connectivityResults; - - // 处理不同类型的输入 - if (results is ConnectivityResult) { - connectivityResults = [results]; - } else if (results is List) { - connectivityResults = results; - } else { - debugPrint('NetworkMonitor: Invalid results type: ${results.runtimeType}'); - return; - } - debugPrint('NetworkMonitor: Updating status with results: $results'); - - NetworkType type = NetworkType.none; - bool isConnected = false; - bool isMetered = false; - - // 检查是否有有效的网络连接 - if (results.isNotEmpty && !results.contains(ConnectivityResult.none)) { - isConnected = true; - - // 确定网络类型(优先级:WiFi > Ethernet > Mobile > VPN > Bluetooth > Other) - if (results.contains(ConnectivityResult.wifi)) { - type = NetworkType.wifi; - isMetered = false; - debugPrint('NetworkMonitor: Detected WiFi connection'); - } else if (results.contains(ConnectivityResult.ethernet)) { - type = NetworkType.ethernet; - isMetered = false; - debugPrint('NetworkMonitor: Detected Ethernet connection'); - } else if (results.contains(ConnectivityResult.mobile)) { - type = NetworkType.mobile; - isMetered = true; - debugPrint('NetworkMonitor: Detected Mobile connection'); - } else if (results.contains(ConnectivityResult.vpn)) { - type = NetworkType.vpn; - isMetered = false; - debugPrint('NetworkMonitor: Detected VPN connection'); - } else if (results.contains(ConnectivityResult.bluetooth)) { - type = NetworkType.bluetooth; - isMetered = true; - debugPrint('NetworkMonitor: Detected Bluetooth connection'); - } else { - type = NetworkType.other; - isMetered = true; - debugPrint('NetworkMonitor: Detected Other connection type'); - } - } else { - debugPrint('NetworkMonitor: No network connection detected'); - type = NetworkType.none; - isConnected = false; - } - - // 更新当前状态 - final previousStatus = _currentStatus; - _currentStatus = NetworkStatus( - type: type, - quality: _currentStatus?.quality ?? NetworkQuality.unknown, - isConnected: isConnected, - isMetered: isMetered, - downloadSpeed: _currentStatus?.downloadSpeed, - uploadSpeed: _currentStatus?.uploadSpeed, - ping: _currentStatus?.ping, - ); - - debugPrint('NetworkMonitor: Status updated - Type: $type, Connected: $isConnected, Metered: $isMetered'); - - // 只有在状态真正改变时才发送通知 - if (previousStatus == null || - previousStatus.type != type || - previousStatus.isConnected != isConnected || - previousStatus.isMetered != isMetered) { - _statusController.add(_currentStatus!); - debugPrint('NetworkMonitor: Status change detected, notifying listeners'); - } - - // 如果已连接,开始网络质量测试 - if (isConnected && (type == NetworkType.wifi || type == NetworkType.ethernet)) { - _testNetworkQuality(); - } - } - - /// 开始网络质量检查 - void _startQualityCheck() { - _qualityCheckTimer?.cancel(); - _qualityCheckTimer = Timer.periodic(const Duration(seconds: 30), (_) { - if (_currentStatus?.isConnected == true) { - _testNetworkQuality(); - } - }); - } - - /// 测试网络质量 - Future _testNetworkQuality() async { - try { - // 测试延迟 - final ping = await _testPing(); - - // 测试下载速度 - final downloadSpeed = await _testDownloadSpeed(); - - // 计算网络质量 - final quality = _calculateQuality(downloadSpeed, ping); - - _currentStatus = NetworkStatus( - type: _currentStatus?.type ?? NetworkType.other, - quality: quality, - isConnected: _currentStatus?.isConnected ?? true, - isMetered: _currentStatus?.isMetered ?? false, - downloadSpeed: downloadSpeed, - uploadSpeed: _currentStatus?.uploadSpeed, - ping: ping, - ); - - _statusController.add(_currentStatus!); - } catch (e) { - debugPrint('Failed to test network quality: $e'); - } - } - - /// 测试延迟 - Future _testPing() async { - try { - final stopwatch = Stopwatch()..start(); - final socket = await Socket.connect('8.8.8.8', 53, timeout: const Duration(seconds: 5)); - socket.destroy(); - stopwatch.stop(); - return stopwatch.elapsedMilliseconds; - } catch (e) { - return 9999; // 超时或失败 - } - } - - /// 测试下载速度 - Future _testDownloadSpeed() async { - try { - // 使用小文件测试速度 - const testUrl = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'; - final stopwatch = Stopwatch()..start(); - - final client = HttpClient(); - client.connectionTimeout = const Duration(seconds: 5); - - final request = await client.getUrl(Uri.parse(testUrl)); - final response = await request.close(); - - double bytes = 0; - await for (final chunk in response) { - bytes += chunk.length; - } - - stopwatch.stop(); - final seconds = stopwatch.elapsedMilliseconds / 1000; - final speed = bytes / seconds; // bytes per second - - // 添加到采样列表 - _speedSamples.add(speed); - if (_speedSamples.length > _maxSamples) { - _speedSamples.removeAt(0); - } - - // 返回平均速度 - final avgSpeed = _speedSamples.reduce((a, b) => a + b) / _speedSamples.length; - return avgSpeed; - } catch (e) { - return 0; - } - } - - /// 计算网络质量 - NetworkQuality _calculateQuality(double downloadSpeed, int ping) { - // 基于下载速度和延迟综合评估 - if (downloadSpeed == 0 || ping > 1000) { - return NetworkQuality.poor; - } - - // 速度权重70%,延迟权重30% - double score = 0; - - // 速度评分 (0-70) - if (downloadSpeed > 2 * 1024 * 1024) { - // > 2MB/s - score += 70; - } else if (downloadSpeed > 500 * 1024) { - // > 500KB/s - score += 50; - } else if (downloadSpeed > 100 * 1024) { - // > 100KB/s - score += 30; - } else { - score += 10; - } - - // 延迟评分 (0-30) - if (ping < 50) { - score += 30; - } else if (ping < 100) { - score += 20; - } else if (ping < 200) { - score += 10; - } else { - score += 5; - } - - // 根据总分判断质量 - if (score >= 80) { - return NetworkQuality.excellent; - } else if (score >= 60) { - return NetworkQuality.good; - } else if (score >= 40) { - return NetworkQuality.moderate; - } else { - return NetworkQuality.poor; - } - } - - /// 获取当前网络状态 - NetworkStatus? get currentStatus => _currentStatus; - - /// 手动刷新网络状态 - Future refreshNetworkStatus() async { - debugPrint('NetworkMonitor: Manual refresh requested'); - try { - final result = await _connectivity.checkConnectivity(); - debugPrint('NetworkMonitor: Manual refresh result: $result'); - await _updateStatus(result); - } catch (e) { - debugPrint('NetworkMonitor: Manual refresh failed: $e'); - await _updateStatus(ConnectivityResult.none); - } - } - - /// 是否已连接 - bool get isConnected => _currentStatus?.isConnected ?? false; - - /// 是否WiFi连接 - bool get isWifi => _currentStatus?.type == NetworkType.wifi; - - /// 是否移动网络 - bool get isMobile => _currentStatus?.type == NetworkType.mobile; - - /// 是否计费网络 - bool get isMetered => _currentStatus?.isMetered ?? false; - - /// 等待网络连接 - Future waitForConnection({Duration? timeout}) async { - if (isConnected) return true; - - final completer = Completer(); - StreamSubscription? subscription; - Timer? timer; - - subscription = statusStream.listen((status) { - if (status.isConnected) { - completer.complete(true); - subscription?.cancel(); - timer?.cancel(); - } - }); - - if (timeout != null) { - timer = Timer(timeout, () { - if (!completer.isCompleted) { - completer.complete(false); - subscription?.cancel(); - } - }); - } - - return completer.future; - } - - /// 等待WiFi连接 - Future waitForWifi({Duration? timeout}) async { - if (isWifi) return true; - - final completer = Completer(); - StreamSubscription? subscription; - Timer? timer; - - subscription = statusStream.listen((status) { - if (status.type == NetworkType.wifi) { - completer.complete(true); - subscription?.cancel(); - timer?.cancel(); - } - }); - - if (timeout != null) { - timer = Timer(timeout, () { - if (!completer.isCompleted) { - completer.complete(false); - subscription?.cancel(); - } - }); - } - - return completer.future; - } - - /// 检查是否可以下载 - bool canDownload({required bool wifiOnly, required bool allowCellular}) { - if (!isConnected) return false; - - if (wifiOnly && !isWifi) return false; - - if (!allowCellular && isMobile) return false; - - return true; - } - - /// 获取建议的下载策略 - Map getSuggestedDownloadStrategy() { - if (!isConnected) { - return {'canDownload': false, 'reason': 'No network connection'}; - } - - final quality = _currentStatus?.quality ?? NetworkQuality.unknown; - final type = _currentStatus?.type ?? NetworkType.other; - - return { - 'canDownload': true, - 'useParallelDownload': quality == NetworkQuality.excellent, - 'maxConcurrentChunks': _getSuggestedChunks(quality), - 'chunkSize': _getSuggestedChunkSize(quality), - 'shouldCompress': type == NetworkType.mobile, - 'recommendedTimeout': _getSuggestedTimeout(quality), - }; - } - - int _getSuggestedChunks(NetworkQuality quality) { - switch (quality) { - case NetworkQuality.excellent: - return 5; - case NetworkQuality.good: - return 3; - case NetworkQuality.moderate: - return 2; - default: - return 1; - } - } - - int _getSuggestedChunkSize(NetworkQuality quality) { - switch (quality) { - case NetworkQuality.excellent: - return 2 * 1024 * 1024; // 2MB - case NetworkQuality.good: - return 1024 * 1024; // 1MB - case NetworkQuality.moderate: - return 512 * 1024; // 512KB - default: - return 256 * 1024; // 256KB - } - } - - int _getSuggestedTimeout(NetworkQuality quality) { - switch (quality) { - case NetworkQuality.excellent: - return 30; - case NetworkQuality.good: - return 60; - case NetworkQuality.moderate: - return 120; - default: - return 300; - } - } - - /// 释放资源 - void dispose() { - _connectivitySubscription?.cancel(); - _qualityCheckTimer?.cancel(); - _statusController.close(); - } -} diff --git a/lib/core/notification_helper.dart b/lib/core/notification_helper.dart deleted file mode 100644 index 06a10bf..0000000 --- a/lib/core/notification_helper.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; - -/// 通知助手类,用于显示下载进度 -class NotificationHelper { - static final NotificationHelper _instance = NotificationHelper._(); - static NotificationHelper get instance => _instance; - NotificationHelper._(); - - final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - - /// 初始化 - Future initialize(Future Function(NotificationResponse)? onDidReceiveNotificationResponse) async { - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('@mipmap/ic_launcher'); // 使用App图标 - - const InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - ); - - await _flutterLocalNotificationsPlugin.initialize( - initializationSettings, - onDidReceiveNotificationResponse: onDidReceiveNotificationResponse, - ); - } - - /// 显示下载进度通知 - Future showDownloadProgressNotification({ - required int progress, - required String title, - required String body, - }) async { - final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'app_upgrade_download_channel', - '下载通知', - channelDescription: '显示应用更新的下载进度', - importance: Importance.low, // 设置为 low,避免下载过程中声音干扰 - priority: Priority.low, - showProgress: true, - maxProgress: 100, - progress: progress, - onlyAlertOnce: true, // 只在首次显示时响铃 - ); - final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); - - await _flutterLocalNotificationsPlugin.show( - 0, // 使用固定的 ID,以便更新通知 - title, - body, - platformChannelSpecifics, - payload: 'download_progress', - ); - } - - /// 显示下载完成通知 - Future showDownloadCompleteNotification({ - required String title, - required String body, - required String filePath, - }) async { - final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'app_upgrade_download_channel', // 与进度通知使用相同渠道 - '下载通知', - channelDescription: '显示应用更新的下载进度', - importance: Importance.high, // 完成时使用高优先级,以便用户看到 - priority: Priority.high, - autoCancel: true, - ); - final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); - - await _flutterLocalNotificationsPlugin.show( - 0, // ID 仍然为 0,覆盖之前的进度通知 - title, - body, - platformChannelSpecifics, - payload: 'download_complete:$filePath', // payload 中带上文件路径 - ); - } - - /// 显示下载失败通知 - Future showDownloadFailedNotification({ - required String title, - required String body, - }) async { - // 与完成通知类似,但内容不同 - final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'app_upgrade_download_channel', - '下载通知', - channelDescription: '显示应用更新的下载进度', - importance: Importance.high, - priority: Priority.high, - autoCancel: true, - ); - final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); - - await _flutterLocalNotificationsPlugin.show( - 0, - title, - body, - platformChannelSpecifics, - payload: 'download_failed', - ); - } - - /// 取消通知 - Future cancelNotification() async { - await _flutterLocalNotificationsPlugin.cancel(0); - } -} diff --git a/lib/core/upgrade_config.dart b/lib/core/upgrade_config.dart deleted file mode 100644 index 95f56b5..0000000 --- a/lib/core/upgrade_config.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:flutter/foundation.dart'; - -/// 升级配置类 -class UpgradeConfig { - /// 单例实例 - static UpgradeConfig? _instance; - - /// 获取单例实例 - static UpgradeConfig get instance { - _instance ??= UpgradeConfig._(); - return _instance!; - } - - UpgradeConfig._(); - - /// 是否启用调试模式 - bool debugMode = kDebugMode; - - /// 检查更新的间隔时间(小时) - int checkIntervalHours = 24; - - /// 是否自动检查更新 - bool autoCheck = true; - - /// 是否只在WiFi下自动下载 - bool wifiOnly = true; - - /// 下载超时时间(秒) - int downloadTimeout = 300; - - /// 连接超时时间(秒) - int connectTimeout = 30; - - /// 最大重试次数 - int maxRetryCount = 3; - - /// 重试延迟时间(秒) - int retryDelay = 5; - - /// 是否使用缓存 - bool useCache = true; - - /// 缓存有效期(小时) - int cacheValidHours = 1; - - /// 是否显示下载通知(Android) - bool showNotification = true; - - /// 是否支持断点续传 - bool supportBreakpoint = true; - - /// 下载块大小(字节) - int chunkSize = 1024 * 1024; // 1MB - - /// 最大并发下载数 - int maxConcurrentDownloads = 3; - - /// 是否校验文件完整性 - bool verifyIntegrity = true; - - /// 是否允许移动网络下载 - bool allowCellular = false; - - /// 是否静默下载(后台下载) - bool silentDownload = false; - - /// 是否保存下载历史 - bool saveDownloadHistory = true; - - /// 自定义请求头 - Map customHeaders = {}; - - /// 代理设置 - String? proxyUrl; - - /// 更新配置 - void updateConfig({ - bool? debugMode, - int? checkIntervalHours, - bool? autoCheck, - bool? wifiOnly, - int? downloadTimeout, - int? connectTimeout, - int? maxRetryCount, - int? retryDelay, - bool? useCache, - int? cacheValidHours, - bool? showNotification, - bool? supportBreakpoint, - int? chunkSize, - int? maxConcurrentDownloads, - bool? verifyIntegrity, - bool? allowCellular, - bool? silentDownload, - bool? saveDownloadHistory, - Map? customHeaders, - String? proxyUrl, - }) { - if (debugMode != null) this.debugMode = debugMode; - if (checkIntervalHours != null) this.checkIntervalHours = checkIntervalHours; - if (autoCheck != null) this.autoCheck = autoCheck; - if (wifiOnly != null) this.wifiOnly = wifiOnly; - if (downloadTimeout != null) this.downloadTimeout = downloadTimeout; - if (connectTimeout != null) this.connectTimeout = connectTimeout; - if (maxRetryCount != null) this.maxRetryCount = maxRetryCount; - if (retryDelay != null) this.retryDelay = retryDelay; - if (useCache != null) this.useCache = useCache; - if (cacheValidHours != null) this.cacheValidHours = cacheValidHours; - if (showNotification != null) this.showNotification = showNotification; - if (supportBreakpoint != null) this.supportBreakpoint = supportBreakpoint; - if (chunkSize != null) this.chunkSize = chunkSize; - if (maxConcurrentDownloads != null) this.maxConcurrentDownloads = maxConcurrentDownloads; - if (verifyIntegrity != null) this.verifyIntegrity = verifyIntegrity; - if (allowCellular != null) this.allowCellular = allowCellular; - if (silentDownload != null) this.silentDownload = silentDownload; - if (saveDownloadHistory != null) this.saveDownloadHistory = saveDownloadHistory; - if (customHeaders != null) this.customHeaders = customHeaders; - if (proxyUrl != null) this.proxyUrl = proxyUrl; - } - - /// 重置为默认配置 - void reset() { - debugMode = kDebugMode; - checkIntervalHours = 24; - autoCheck = true; - wifiOnly = true; - downloadTimeout = 300; - connectTimeout = 30; - maxRetryCount = 3; - retryDelay = 5; - useCache = true; - cacheValidHours = 1; - showNotification = true; - supportBreakpoint = true; - chunkSize = 1024 * 1024; - maxConcurrentDownloads = 3; - verifyIntegrity = true; - allowCellular = false; - silentDownload = false; - saveDownloadHistory = true; - customHeaders = {}; - proxyUrl = null; - } - - /// 导出配置为Map - Map toMap() { - return { - 'debugMode': debugMode, - 'checkIntervalHours': checkIntervalHours, - 'autoCheck': autoCheck, - 'wifiOnly': wifiOnly, - 'downloadTimeout': downloadTimeout, - 'connectTimeout': connectTimeout, - 'maxRetryCount': maxRetryCount, - 'retryDelay': retryDelay, - 'useCache': useCache, - 'cacheValidHours': cacheValidHours, - 'showNotification': showNotification, - 'supportBreakpoint': supportBreakpoint, - 'chunkSize': chunkSize, - 'maxConcurrentDownloads': maxConcurrentDownloads, - 'verifyIntegrity': verifyIntegrity, - 'allowCellular': allowCellular, - 'silentDownload': silentDownload, - 'saveDownloadHistory': saveDownloadHistory, - 'customHeaders': customHeaders, - 'proxyUrl': proxyUrl, - }; - } - - /// 从Map导入配置 - void fromMap(Map map) { - updateConfig( - debugMode: map['debugMode'], - checkIntervalHours: map['checkIntervalHours'], - autoCheck: map['autoCheck'], - wifiOnly: map['wifiOnly'], - downloadTimeout: map['downloadTimeout'], - connectTimeout: map['connectTimeout'], - maxRetryCount: map['maxRetryCount'], - retryDelay: map['retryDelay'], - useCache: map['useCache'], - cacheValidHours: map['cacheValidHours'], - showNotification: map['showNotification'], - supportBreakpoint: map['supportBreakpoint'], - chunkSize: map['chunkSize'], - maxConcurrentDownloads: map['maxConcurrentDownloads'], - verifyIntegrity: map['verifyIntegrity'], - allowCellular: map['allowCellular'], - silentDownload: map['silentDownload'], - saveDownloadHistory: map['saveDownloadHistory'], - customHeaders: map['customHeaders'] != null ? Map.from(map['customHeaders']) : null, - proxyUrl: map['proxyUrl'], - ); - } -} - -/// 升级策略 -enum UpgradeStrategy { - /// 立即升级 - immediate, - - /// 空闲时升级 - idle, - - /// 定时升级 - scheduled, - - /// 静默升级 - silent, - - /// 灰度升级 - grayScale, -} - -/// 版本比较策略 -enum VersionCompareStrategy { - /// 数字比较(如:1.2.3) - numeric, - - /// 语义化版本比较(如:1.2.3-beta.1) - semantic, - - /// 时间戳比较 - timestamp, - - /// 构建号比较 - buildNumber, - - /// 自定义比较 - custom, -} diff --git a/lib/core/version_comparator.dart b/lib/core/version_comparator.dart deleted file mode 100644 index 16559fd..0000000 --- a/lib/core/version_comparator.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'upgrade_config.dart'; - -/// 版本信息 -class Version { - final String raw; - final int? major; - final int? minor; - final int? patch; - final String? preRelease; - final String? buildMetadata; - final int? buildNumber; - final DateTime? timestamp; - - Version({ - required this.raw, - this.major, - this.minor, - this.patch, - this.preRelease, - this.buildMetadata, - this.buildNumber, - this.timestamp, - }); - - /// 从字符串解析版本 - factory Version.parse(String version) { - final raw = version.trim(); - - // 尝试解析语义化版本 (1.2.3-beta.1+build.123) - final semanticRegex = RegExp(r'^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\.\-]+))?(?:\+([a-zA-Z0-9\.\-]+))?$'); - - final match = semanticRegex.firstMatch(raw); - if (match != null) { - return Version( - raw: raw, - major: int.tryParse(match.group(1)!), - minor: int.tryParse(match.group(2)!), - patch: int.tryParse(match.group(3)!), - preRelease: match.group(4), - buildMetadata: match.group(5), - ); - } - - // 先尝试解析为纯数字构建号 - final buildNumber = int.tryParse(raw); - if (buildNumber != null && !raw.contains('.')) { - return Version(raw: raw, buildNumber: buildNumber); - } - - // 尝试解析简单版本 (1.2.3) - final simpleRegex = RegExp(r'^(\d+)(?:\.(\d+))?(?:\.(\d+))?$'); - final simpleMatch = simpleRegex.firstMatch(raw); - if (simpleMatch != null) { - return Version( - raw: raw, - major: int.tryParse(simpleMatch.group(1)!), - minor: simpleMatch.group(2) != null ? int.tryParse(simpleMatch.group(2)!) : null, - patch: simpleMatch.group(3) != null ? int.tryParse(simpleMatch.group(3)!) : null, - ); - } - - // 尝试解析为时间戳 - final timestamp = DateTime.tryParse(raw); - if (timestamp != null) { - return Version(raw: raw, timestamp: timestamp); - } - - // 无法解析,返回原始字符串 - return Version(raw: raw); - } - - /// 转换为字符串 - @override - String toString() => raw; - - /// 是否是预发布版本 - bool get isPreRelease => preRelease != null && preRelease!.isNotEmpty; - - /// 获取版本数组 - List get versionArray { - final arr = []; - if (major != null) arr.add(major!); - if (minor != null) arr.add(minor!); - if (patch != null) arr.add(patch!); - return arr; - } -} - -/// 版本比较器 -class VersionComparator { - final VersionCompareStrategy strategy; - final Function(Version, Version)? customComparator; - - VersionComparator({this.strategy = VersionCompareStrategy.semantic, this.customComparator}); - - /// 比较两个版本 - /// 返回: 1 表示 v1 > v2, 0 表示相等, -1 表示 v1 < v2 - int compare(String version1, String version2) { - if (version1 == version2) return 0; - - final v1 = Version.parse(version1); - final v2 = Version.parse(version2); - - switch (strategy) { - case VersionCompareStrategy.numeric: - return _compareNumeric(v1, v2); - case VersionCompareStrategy.semantic: - return _compareSemantic(v1, v2); - case VersionCompareStrategy.timestamp: - return _compareTimestamp(v1, v2); - case VersionCompareStrategy.buildNumber: - return _compareBuildNumber(v1, v2); - case VersionCompareStrategy.custom: - if (customComparator != null) { - return customComparator!(v1, v2); - } - return _compareSemantic(v1, v2); - } - } - - /// 数字比较 - int _compareNumeric(Version v1, Version v2) { - final arr1 = v1.versionArray; - final arr2 = v2.versionArray; - - if (arr1.isEmpty && arr2.isEmpty) { - return v1.raw.compareTo(v2.raw); - } - - if (arr1.isEmpty) return -1; - if (arr2.isEmpty) return 1; - - final maxLength = arr1.length > arr2.length ? arr1.length : arr2.length; - - for (int i = 0; i < maxLength; i++) { - final num1 = i < arr1.length ? arr1[i] : 0; - final num2 = i < arr2.length ? arr2[i] : 0; - - if (num1 > num2) return 1; - if (num1 < num2) return -1; - } - - return 0; - } - - /// 语义化版本比较 - int _compareSemantic(Version v1, Version v2) { - // 先比较主版本号 - final majorCompare = _compareInt(v1.major, v2.major); - if (majorCompare != 0) return majorCompare; - - // 比较次版本号 - final minorCompare = _compareInt(v1.minor, v2.minor); - if (minorCompare != 0) return minorCompare; - - // 比较修订号 - final patchCompare = _compareInt(v1.patch, v2.patch); - if (patchCompare != 0) return patchCompare; - - // 比较预发布版本 - if (v1.preRelease != null && v2.preRelease == null) return -1; - if (v1.preRelease == null && v2.preRelease != null) return 1; - if (v1.preRelease != null && v2.preRelease != null) { - return _comparePreRelease(v1.preRelease!, v2.preRelease!); - } - - return 0; - } - - /// 比较预发布版本 - int _comparePreRelease(String pre1, String pre2) { - final parts1 = pre1.split('.'); - final parts2 = pre2.split('.'); - - final maxLength = parts1.length > parts2.length ? parts1.length : parts2.length; - - for (int i = 0; i < maxLength; i++) { - if (i >= parts1.length) return -1; - if (i >= parts2.length) return 1; - - final part1 = parts1[i]; - final part2 = parts2[i]; - - // 尝试数字比较 - final num1 = int.tryParse(part1); - final num2 = int.tryParse(part2); - - if (num1 != null && num2 != null) { - if (num1 > num2) return 1; - if (num1 < num2) return -1; - } else { - // 字符串比较 - final compare = part1.compareTo(part2); - if (compare != 0) return compare; - } - } - - return 0; - } - - /// 时间戳比较 - int _compareTimestamp(Version v1, Version v2) { - if (v1.timestamp == null && v2.timestamp == null) { - return _compareSemantic(v1, v2); - } - - if (v1.timestamp == null) return -1; - if (v2.timestamp == null) return 1; - - return v1.timestamp!.compareTo(v2.timestamp!); - } - - /// 构建号比较 - int _compareBuildNumber(Version v1, Version v2) { - if (v1.buildNumber == null && v2.buildNumber == null) { - return _compareSemantic(v1, v2); - } - - if (v1.buildNumber == null) return -1; - if (v2.buildNumber == null) return 1; - - if (v1.buildNumber! > v2.buildNumber!) return 1; - if (v1.buildNumber! < v2.buildNumber!) return -1; - - return 0; - } - - /// 比较两个可空整数 - int _compareInt(int? a, int? b) { - if (a == null && b == null) return 0; - if (a == null) return -1; - if (b == null) return 1; - if (a > b) return 1; - if (a < b) return -1; - return 0; - } - - /// 检查是否需要更新 - bool isUpdateAvailable(String currentVersion, String remoteVersion) { - return compare(remoteVersion, currentVersion) > 0; - } - - /// 检查是否是主要版本更新 - bool isMajorUpdate(String currentVersion, String remoteVersion) { - final v1 = Version.parse(currentVersion); - final v2 = Version.parse(remoteVersion); - - if (v1.major != null && v2.major != null) { - return v2.major! > v1.major!; - } - - return false; - } - - /// 检查是否是次要版本更新 - bool isMinorUpdate(String currentVersion, String remoteVersion) { - final v1 = Version.parse(currentVersion); - final v2 = Version.parse(remoteVersion); - - if (v1.major != null && v2.major != null && v1.minor != null && v2.minor != null) { - return v2.major! == v1.major! && v2.minor! > v1.minor!; - } - - return false; - } - - /// 检查是否是修订版本更新 - bool isPatchUpdate(String currentVersion, String remoteVersion) { - final v1 = Version.parse(currentVersion); - final v2 = Version.parse(remoteVersion); - - if (v1.major != null && - v2.major != null && - v1.minor != null && - v2.minor != null && - v1.patch != null && - v2.patch != null) { - return v2.major! == v1.major! && v2.minor! == v1.minor! && v2.patch! > v1.patch!; - } - - return false; - } - - /// 获取版本差异描述 - String getVersionDifference(String currentVersion, String remoteVersion) { - if (isMajorUpdate(currentVersion, remoteVersion)) { - return '主要版本更新'; - } else if (isMinorUpdate(currentVersion, remoteVersion)) { - return '功能更新'; - } else if (isPatchUpdate(currentVersion, remoteVersion)) { - return 'Bug修复'; - } else if (isUpdateAvailable(currentVersion, remoteVersion)) { - return '新版本'; - } else { - return '已是最新版本'; - } - } - - /// 批量比较版本,返回最新版本 - String? getLatestVersion(List versions) { - if (versions.isEmpty) return null; - if (versions.length == 1) return versions.first; - - String latest = versions.first; - for (final version in versions.skip(1)) { - if (compare(version, latest) > 0) { - latest = version; - } - } - - return latest; - } - - /// 按版本排序 - List sortVersions(List versions, {bool descending = false}) { - final sorted = List.from(versions); - sorted.sort((a, b) => descending ? compare(b, a) : compare(a, b)); - return sorted; - } - - /// 过滤出符合条件的版本 - List filterVersions( - List versions, { - String? minVersion, - String? maxVersion, - bool includePreRelease = false, - }) { - return versions.where((version) { - // 检查最小版本 - if (minVersion != null && compare(version, minVersion) < 0) { - return false; - } - - // 检查最大版本 - if (maxVersion != null && compare(version, maxVersion) > 0) { - return false; - } - - // 检查预发布版本 - if (!includePreRelease) { - final v = Version.parse(version); - if (v.isPreRelease) { - return false; - } - } - - return true; - }).toList(); - } -} diff --git a/lib/widgets/download_progress_dialog.dart b/lib/widgets/download_progress_dialog.dart deleted file mode 100644 index 60cdee0..0000000 --- a/lib/widgets/download_progress_dialog.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../app_upgrade_plugin.dart'; -import '../core/upgrade_utils.dart'; - -/// 下载进度对话框 -class DownloadProgressDialog extends StatefulWidget { - final String downloadUrl; - final Color? primaryColor; - - const DownloadProgressDialog({super.key, required this.downloadUrl, this.primaryColor}); - - /// 显示下载进度对话框 - static Future show(BuildContext context, {required String downloadUrl, Color? primaryColor}) { - return showDialog( - context: context, - barrierDismissible: false, - builder: (context) => DownloadProgressDialog(downloadUrl: downloadUrl, primaryColor: primaryColor), - ); - } - - @override - State createState() => _DownloadProgressDialogState(); -} - -class _DownloadProgressDialogState extends State { - final AppUpgradePlugin _plugin = AppUpgradePlugin(); - - double _progress = 0.0; - String _progressText = '准备下载...'; - int _received = 0; - int _total = 0; - bool _isDownloading = false; - String? _errorMessage; - - @override - void initState() { - super.initState(); - _startDownload(); - } - - @override - Widget build(BuildContext context) { - final primaryColor = widget.primaryColor ?? Theme.of(context).primaryColor; - - return WillPopScope( - onWillPop: () async => false, - child: Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 图标 - Icon( - _errorMessage != null ? Icons.error_outline : Icons.download_rounded, - size: 48, - color: _errorMessage != null ? Colors.red : primaryColor, - ), - const SizedBox(height: 16), - - // 标题 - Text( - _errorMessage != null ? '下载失败' : '正在下载更新', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - - if (_errorMessage != null) ...[ - // 错误信息 - Text( - _errorMessage!, - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - - // 按钮 - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(null); - }, - child: const Text('取消'), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton( - onPressed: () { - setState(() { - _errorMessage = null; - _progress = 0.0; - _progressText = '准备下载...'; - }); - _startDownload(); - }, - style: ElevatedButton.styleFrom(backgroundColor: primaryColor), - child: const Text('重试'), - ), - ), - ], - ), - ] else ...[ - // 进度条 - LinearProgressIndicator( - value: _isDownloading ? _progress : null, - backgroundColor: Colors.grey[300], - valueColor: AlwaysStoppedAnimation(primaryColor), - ), - const SizedBox(height: 12), - - // 进度文本 - Text(_progressText, style: TextStyle(fontSize: 14, color: Colors.grey[600])), - const SizedBox(height: 8), - - // 文件大小信息 - if (_total > 0) - Text( - '${formatBytes(_received)} / ${formatBytes(_total)}', - style: TextStyle(fontSize: 12, color: Colors.grey[500]), - ), - ], - ], - ), - ), - ), - ); - } - - Future _startDownload() async { - setState(() { - _isDownloading = true; - _errorMessage = null; - }); - - try { - final filePath = await _plugin.downloadApk( - widget.downloadUrl, - onProgress: (progress) { - setState(() { - _progress = progress.progress; - _received = progress.received; - _total = progress.total; - _progressText = '下载中 ${progress.percentage}%'; - }); - }, - ); - - if (filePath != null) { - setState(() { - _progressText = '下载完成'; - }); - - // 延迟一下让用户看到完成状态 - await Future.delayed(const Duration(milliseconds: 500)); - - // 返回文件路径 - if (mounted) { - Navigator.of(context).pop(filePath); - } - } else { - setState(() { - _errorMessage = '下载失败,请检查网络连接'; - _isDownloading = false; - }); - } - } catch (e) { - setState(() { - _errorMessage = '下载出错:${e.toString()}'; - _isDownloading = false; - }); - } - } -} diff --git a/lib/widgets/upgrade_dialog.dart b/lib/widgets/upgrade_dialog.dart deleted file mode 100644 index a8c2401..0000000 --- a/lib/widgets/upgrade_dialog.dart +++ /dev/null @@ -1,195 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; - -import '../app_upgrade_plugin.dart'; -import '../core/upgrade_utils.dart'; - -/// App升级对话框 -class UpgradeDialog extends StatefulWidget { - final UpgradeInfo upgradeInfo; - final VoidCallback? onCancel; - final VoidCallback? onConfirm; - final Color? primaryColor; - - const UpgradeDialog({super.key, required this.upgradeInfo, this.onCancel, this.onConfirm, this.primaryColor}); - - /// 显示升级对话框 - static Future show(BuildContext context, {required UpgradeInfo upgradeInfo, Color? primaryColor}) { - return showDialog( - context: context, - barrierDismissible: !upgradeInfo.isForceUpdate, - builder: (BuildContext context) { - return UpgradeDialog( - upgradeInfo: upgradeInfo, - primaryColor: primaryColor, - ); - }, - ); - } - - @override - State createState() => _UpgradeDialogState(); -} - -class _UpgradeDialogState extends State { - final AppUpgradePlugin _plugin = AppUpgradePlugin(); - bool _isDownloading = false; - - @override - Widget build(BuildContext context) { - final primaryColor = widget.primaryColor ?? Theme.of(context).primaryColor; - - return WillPopScope( - onWillPop: () async => !widget.upgradeInfo.isForceUpdate && !_isDownloading, - child: Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 顶部图片或图标 - Container( - height: 120, - decoration: BoxDecoration( - color: primaryColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.system_update, size: 48, color: Colors.white), - const SizedBox(height: 8), - Text( - '发现新版本 ${widget.upgradeInfo.versionName}', - style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), - ), - ], - ), - ), - ), - - // 更新内容 - Container( - padding: const EdgeInsets.all(16), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('更新内容:', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Text(widget.upgradeInfo.updateContent, style: const TextStyle(fontSize: 14, height: 1.5)), - ], - ), - ), - ), - - // 文件大小提示(如果有) - if (widget.upgradeInfo.apkSize != null && Platform.isAndroid) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - '新版本大小:${formatBytes(widget.upgradeInfo.apkSize!)}', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), - ), - ), - - const SizedBox(height: 16), - - // 按钮 - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - if (!widget.upgradeInfo.isForceUpdate) ...[ - Expanded( - child: TextButton( - onPressed: _isDownloading - ? null - : () { - widget.onCancel?.call(); - Navigator.of(context).pop(false); - }, - child: Text('稍后更新', style: TextStyle(color: Colors.grey[600])), - ), - ), - const SizedBox(width: 16), - ], - Expanded( - child: ElevatedButton( - onPressed: _isDownloading ? null : () => _handleConfirm(), - style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), - ), - child: const Text('立即更新'), - ), - ), - ], - ), - ), - const SizedBox(height: 8), - ], - ), - ), - ); - } - - void _handleConfirm() async { - widget.onConfirm?.call(); - - if (Platform.isAndroid) { - // Android平台:下载并安装APK - if (widget.upgradeInfo.downloadUrl != null) { - setState(() { - _isDownloading = true; - }); - - // 显示下载进度对话框 - final filePath = await DownloadProgressDialog.show( - context, - downloadUrl: widget.upgradeInfo.downloadUrl!, - primaryColor: widget.primaryColor, - ); - - setState(() { - _isDownloading = false; - }); - - if (filePath != null) { - // 安装前再次检查并请求权限 - final hasInstallPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context); - if (!hasInstallPermission) { - _showError('未授予安装权限,无法完成更新'); - return; - } - - // 安装APK - final success = await _plugin.installApk(filePath); - if (success) { - if (!widget.upgradeInfo.isForceUpdate) { - Navigator.of(context).pop(true); - } - } else { - _showError('安装失败,请检查权限设置'); - } - } - } - } else if (Platform.isIOS) { - // iOS平台:跳转到App Store - if (widget.upgradeInfo.appStoreUrl != null) { - final success = await _plugin.goToAppStore(widget.upgradeInfo.appStoreUrl!); - if (success) { - Navigator.of(context).pop(true); - } else { - _showError('跳转App Store失败'); - } - } - } - } - - void _showError(String message) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message), backgroundColor: Colors.red)); - } -} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 3b0cb1a..e3435e0 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,2 +1,3 @@ -export 'download_progress_dialog.dart'; -export 'upgrade_dialog.dart'; +// 所有对话框都已整合到 app_upgrade_simple.dart 中 +// 这里保留市场选择对话框的导出 +export 'market_selection_dialog.dart';