import 'dart:io'; import 'package:app_upgrade_plugin/widgets/market_selection_dialog.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'app_upgrade_plugin.dart'; import 'core/upgrade_utils.dart'; /// 简化版App升级管理器 /// 提供最简单的API,一行代码即可实现App升级功能 class AppUpgradeSimple { static AppUpgradeSimple? _instance; /// 获取单例实例 static AppUpgradeSimple get instance { _instance ??= AppUpgradeSimple._(); return _instance!; } @visibleForTesting static set instance(AppUpgradeSimple value) { _instance = value; } @visibleForTesting AppUpgradeSimple.private({AppUpgradePlugin? plugin}) : _plugin = plugin ?? AppUpgradePlugin(); AppUpgradeSimple._() : _plugin = AppUpgradePlugin(); final AppUpgradePlugin _plugin; /// 一键检查更新(最简单的使用方式) /// /// 示例: /// ```dart /// AppUpgradeSimple.instance.checkUpdate( /// context: context, /// url: 'https://api.example.com/check-update', /// ); /// ``` /// /// [context] 需要是一个能够访问到 `Navigator` 和 `MaterialLocalizations` 的有效上下文。 /// 如果您不确定,建议在 `init` 方法中提供 `navigatorKey`,这样插件可以获取一个可靠的上下文。 Future checkUpdate({ required BuildContext context, required String url, Map? params, bool showNoUpdateToast = true, bool autoDownload = false, bool autoInstall = false, VoidCallback? onComplete, }) async { try { assert(_canShowMaterialDialog(context), '请在 MaterialApp 环境内调用'); // 检查更新 final info = await _plugin.checkUpdate(url, params: params); if (info == null || !info.hasUpdate) { if (showNoUpdateToast && context.mounted) _showToast('已是最新版本'); onComplete?.call(); return; } await _showUpgradeDialog( context: context, info: info, autoDownload: autoDownload, autoInstall: autoInstall, onComplete: onComplete, ); } catch (e) { debugPrint('检查更新失败: $e'); String errorMessage = '检查更新失败'; if (e.toString().contains('无网络连接')) { errorMessage = '无网络连接,请检查网络设置'; } else if (e.toString().contains('Failed host lookup')) { errorMessage = '无法连接到服务器,请检查网络或稍后重试'; } else if (e.toString().contains('Connection refused')) { errorMessage = '服务器拒绝连接,请稍后重试'; } else if (e.toString().contains('timeout')) { errorMessage = '连接超时,请检查网络'; } else { errorMessage = '检查更新失败: ${e.toString().split(':').first}'; } if (context.mounted) { _showToast(errorMessage); // 如果是网络问题,显示网络诊断建议 if (e.toString().contains('Failed host lookup') || e.toString().contains('无网络连接')) { debugPrint('建议: 请检查网络连接或尝试使用网络诊断功能'); } } onComplete?.call(); } } /// 在无法显示对话框时的后备逻辑:显示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, Map? params, }) async { try { return await _plugin.checkUpdate(url, params: params); } catch (e) { debugPrint('静默检查更新失败: $e'); return null; } } /// 显示升级对话框 Future _showUpgradeDialog({ required BuildContext context, required UpgradeInfo info, required bool autoDownload, required bool autoInstall, VoidCallback? onComplete, }) { return showDialog( context: context, barrierDismissible: !info.isForceUpdate, builder: (context) { if (info.isForceUpdate) { return _ForceUpgradeDialog(info: info); } else { return _SimpleUpgradeDialog( info: info, autoDownload: autoDownload, autoInstall: autoInstall, onComplete: onComplete, showToast: (message) => _showToast(message), ); } }, ); } /// 显示备用升级对话框(当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); } /// 检查是否存在可用的 Material 环境(仅用于对话框) bool _canShowMaterialDialog(BuildContext context) { if (!context.mounted) return false; try { // Localizations.of will throw if not found. return Localizations.of(context, MaterialLocalizations) != null; } catch (_) { // If it throws, it means we're not in a Material scope. return false; } } /// 共享的安装逻辑 (无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('安装失败,请手动安装'); } } } /// 共享的升级对话框内容构建器 class _UpgradeDialogContent extends StatelessWidget { final UpgradeInfo info; final bool isDownloading; final double downloadProgress; final String statusText; const _UpgradeDialogContent({ required this.info, required this.isDownloading, required this.downloadProgress, required this.statusText, }); @override Widget build(BuildContext context) { final List changeItems = info.updateContent.split(RegExp(r'\r?\n')).map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '版本:${info.versionName}', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), if (info.apkSize != null) ...[ const SizedBox(height: 4), Text('大小:${formatBytes(info.apkSize!)}'), ], const SizedBox(height: 12), const Text('更新内容:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Container( constraints: const BoxConstraints(maxHeight: 220), child: SingleChildScrollView( child: changeItems.isEmpty ? Text(info.updateContent) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: changeItems .map( (line) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• '), Expanded(child: Text(line)), ], ), ), ) .toList(), ), ), ), if (isDownloading) ...[ const SizedBox(height: 16), ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(4)), child: LinearProgressIndicator( value: downloadProgress.clamp(0.0, 1.0), minHeight: 6, ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: Text(statusText, overflow: TextOverflow.ellipsis)), Text('${(downloadProgress * 100).toStringAsFixed(1)}%'), ], ), ], ], ); } } /// 共享的升级操作逻辑 mixin _UpgradeDialogLogic on State { final _plugin = AppUpgradePlugin(); bool _isDownloading = false; double _downloadProgress = 0; String _statusText = ''; UpgradeInfo get info; void Function(String) get showToast; VoidCallback? get onComplete; bool get autoDownload; bool get autoInstall; @override void initState() { super.initState(); if (autoDownload && Platform.isAndroid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _startDownloadAndInstall(); } }); } } Future _startDownloadAndInstall() async { if (!Platform.isAndroid || info.downloadUrl == null) return; if (!mounted) return; final hasStorage = await PermissionHelper.checkAndRequestStoragePermission(context: context); if (!hasStorage) { showToast('缺少存储权限,无法下载'); return; } await PermissionHelper.checkAndRequestNotificationPermission(context: context); setState(() { _isDownloading = true; _statusText = '下载中...'; _downloadProgress = 0; }); final filePath = await _plugin.downloadApk( info.downloadUrl!, onProgress: (p) { if (!mounted) return; setState(() { _downloadProgress = p.progress; _statusText = '下载中 ${p.percentage}%'; }); }, ); if (!mounted) return; if (filePath == null) { setState(() { _isDownloading = false; _statusText = '下载失败'; }); return; } setState(() { _statusText = '下载完成'; _downloadProgress = 1.0; }); if (autoInstall) { await _installApk(filePath); } } Future _installApk(String filePath) async { if (!mounted) return; final hasPermission = await PermissionHelper.checkAndRequestInstallPermission(context: context); if (!hasPermission) { if (mounted) { showToast('未授予安装权限,无法完成更新'); } return; } final success = await _plugin.installApk(filePath); if (!success && mounted) { showToast('安装失败,请手动安装'); } } 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(); } else if (Platform.isIOS) { _handleIosAction(); } else { showToast('Unsupported platform'); } } /// Handles the upgrade action for iOS. void _handleIosAction() { if (info.appStoreUrl != null) { _plugin.goToAppStore(info.appStoreUrl!); // Pop the upgrade dialog. if (Navigator.canPop(context)) { Navigator.of(context).pop(); } onComplete?.call(); } else { showToast('App Store URL is not available.'); } } /// Handles the upgrade action for Android. Future _handleAndroidAction() async { // On Android, we always assume a market option is available, // because we can fall back to a generic market:// intent. 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; } // If a download option is not available, going to the market is the only choice. if (!hasDownloadOption) { _handleMarketAction(); return; } // If a download option exists, always give the user a choice, // as the market option is also implicitly available. await _showDownloadChoiceSheet(); } /// Opens the app store or shows a market selection dialog. Future _performMarketAction() async { final hasMarkets = info.appMarkets?.isNotEmpty ?? false; if (hasMarkets) { await MarketSelectionDialog.show( context, markets: info.appMarkets!, onSelected: (market) { _plugin.goToAppStore(market.url ?? market.packageName ?? ''); }, ); } else { // No specific markets, try a generic market link. final appInfo = await _plugin.getAppInfo(); final pkg = appInfo['packageName'] ?? ''; if (pkg.isNotEmpty) { _plugin.goToAppStore('market://details?id=$pkg'); // _plugin.goToAppStore('market://details?id=$pkg'); } else { showToast('Could not determine app package name.'); } } } /// Pops the current dialog and then performs the market action. Future _handleMarketAction() async { // Pop the upgrade dialog before proceeding. if (Navigator.canPop(context)) { Navigator.of(context).pop(); } if (!mounted) return; await _performMarketAction(); onComplete?.call(); } Future _showDownloadChoiceSheet() async { final bool hasDownload = info.downloadUrl != null; if (!mounted) return; 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') { await _performMarketAction(); onComplete?.call(); return; } if (choice == 'download' && hasDownload && !_isDownloading) { await _startDownloadAndInstall(); } } } /// 简化版升级对话框(非强制更新) class _SimpleUpgradeDialog extends StatefulWidget { final UpgradeInfo info; final bool autoDownload; final bool autoInstall; final VoidCallback? onComplete; final void Function(String) showToast; const _SimpleUpgradeDialog({ required this.info, required this.autoDownload, required this.autoInstall, this.onComplete, required this.showToast, }); @override State<_SimpleUpgradeDialog> createState() => _SimpleUpgradeDialogState(); } class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> 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 theme = Theme.of(context); return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), contentPadding: const EdgeInsets.fromLTRB(20, 8, 20, 16), actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 12), title: Row( children: [ Icon( Icons.system_update, color: theme.colorScheme.primary, ), const SizedBox(width: 8), const Text('发现新版本'), ], ), content: _UpgradeDialogContent( info: widget.info, isDownloading: _isDownloading, downloadProgress: _downloadProgress, statusText: _statusText, ), actionsAlignment: MainAxisAlignment.end, actions: _isDownloading ? [] : [ if (!widget.info.isForceUpdate) TextButton( onPressed: () { Navigator.of(context).pop(); widget.onComplete?.call(); }, child: const Text('稍后更新'), ), ElevatedButton( onPressed: _handleAction, child: Text(Platform.isAndroid ? '立即更新' : '前往更新'), ), ], ); } } /// 强制更新对话框 class _ForceUpgradeDialog extends StatefulWidget { final UpgradeInfo info; const _ForceUpgradeDialog({required this.info}); @override State<_ForceUpgradeDialog> createState() => _ForceUpgradeDialogState(); } class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> { final _plugin = AppUpgradePlugin(); bool _isDownloading = false; double _downloadProgress = 0; String _statusText = ''; @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); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return WillPopScope( onWillPop: () async => false, // 强制更新,不允许返回 child: AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), contentPadding: const EdgeInsets.fromLTRB(20, 8, 20, 16), actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 12), title: Row( children: [ Icon( Icons.system_update, color: theme.colorScheme.primary, ), const SizedBox(width: 8), const Text('发现新版本 (强制)'), ], ), content: _UpgradeDialogContent( info: widget.info, isDownloading: _isDownloading, downloadProgress: _downloadProgress, statusText: _statusText, ), actionsAlignment: MainAxisAlignment.end, actions: _isDownloading ? [] : [ 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('立即更新'), ), ], ), ); } } /// 当 [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; } }