This commit is contained in:
DESKTOP-I3JPKHK\wy 2025-09-19 10:42:46 +08:00
parent 223f225d94
commit 3d1729f630
3 changed files with 1113 additions and 827 deletions

336
USAGE_GUIDE.md Normal file
View File

@ -0,0 +1,336 @@
# App Upgrade Plugin - 完整使用指南
## 🚀 快速开始
### 基础用法(一行代码)
```dart
import 'package:app_upgrade_plugin/app_upgrade_plugin.dart';
// 最简单的使用方式
await AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
);
```
## ⚙️ 配置选项
### 预设配置
```dart
// 自动更新模式
AppUpgradeSimple.instance.configure(UpgradeConfig.auto);
// 静默检查模式
AppUpgradeSimple.instance.configure(UpgradeConfig.silent);
// 开发模式
AppUpgradeSimple.instance.configure(UpgradeConfig.development);
// 生产模式
AppUpgradeSimple.instance.configure(UpgradeConfig.production);
```
### 自定义配置
```dart
AppUpgradeSimple.instance.configure(UpgradeConfig(
showNoUpdateToast: true, // 显示无更新提示
autoDownload: false, // 自动下载
autoInstall: false, // 自动安装
connectionTimeout: 30, // 连接超时(秒)
downloadTimeout: 300, // 下载超时(秒)
installTimeout: 45, // 安装检测超时(秒)
enableDebugLog: true, // 启用调试日志
customToast: (message) { // 自定义Toast
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
));
```
## 🎯 使用场景
### 1. 应用启动时检查更新
```dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) {
// 应用启动后检查更新
WidgetsBinding.instance.addPostFrameCallback((_) {
AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
);
});
return MyHomePage();
},
),
);
}
}
```
### 2. 定期检查更新
```dart
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer? _updateTimer;
@override
void initState() {
super.initState();
// 每24小时检查一次更新
_updateTimer = Timer.periodic(const Duration(hours: 24), (_) {
AppUpgradeSimple.instance.checkUpdateSilent(
url: 'https://api.example.com/check-update',
);
});
}
@override
void dispose() {
_updateTimer?.cancel();
super.dispose();
}
}
```
### 3. 手动检查更新
```dart
ElevatedButton(
onPressed: () async {
await AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
params: {
'userId': currentUserId,
'channel': releaseChannel,
},
onComplete: () {
print('更新检查完成');
},
);
},
child: const Text('检查更新'),
)
```
## 🔧 高级功能
### 预下载APK
```dart
// 后台预下载不显示UI
final filePath = await AppUpgradeSimple.instance.preDownloadApk(
url: 'https://example.com/app.apk',
onProgress: (progress) {
print('下载进度: ${progress.percentage}%');
},
);
```
### 查找已下载的APK
```dart
final existingApk = await AppUpgradeSimple.instance.findDownloadedApk('1.2.0');
if (existingApk != null) {
print('找到已下载的APK: $existingApk');
}
```
### 网络状态检查
```dart
final hasNetwork = await AppUpgradeSimple.instance.checkNetworkStatus();
if (!hasNetwork) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('网络错误'),
content: const Text('请检查网络连接'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('确定'),
),
],
),
);
}
```
### 清理下载缓存
```dart
// 清理所有下载的APK文件
await AppUpgradeSimple.instance.clearDownloadCache();
```
## 🎨 UI定制
### 自定义Toast
```dart
AppUpgradeSimple.instance.configure(UpgradeConfig(
customToast: (message) {
// 使用SnackBar代替Toast
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.deepPurple,
behavior: SnackBarBehavior.floating,
),
);
},
));
```
### 富文本更新内容
服务器返回的更新内容支持简单的富文本格式:
```
**新功能**
- 添加了[暗黑模式]支持
- 优化了`网络请求`性能
- __修复了已知问题__
**重要更新**
请及时更新到最新版本以获得更好的体验!
```
## 📱 平台支持
### Android
- ✅ APK下载和安装
- ✅ 权限自动处理
- ✅ 多应用商店支持
- ✅ 安装状态智能检测
- ✅ 断点续传
- ✅ 文件校验
### iOS
- ✅ App Store跳转
- ✅ 企业证书分发
- ✅ TestFlight支持
## 🔍 调试和测试
### 启用调试日志
```dart
AppUpgradeSimple.instance.configure(UpgradeConfig.development);
```
### 查看日志输出
```
🔍 检查更新结果: UpgradeInfo(...)
⚡ 应用回到前台,立即检查安装状态
✅ 检测结果: 安装成功
❌ 检测结果: 安装未完成
```
### 测试不同场景
插件内置了测试按钮,可以模拟各种安装场景:
- 模拟安装取消
- 模拟权限拒绝
- 模拟安装超时
## 🛡️ 错误处理
插件提供了完善的错误处理机制:
### 网络错误
- 连接超时
- DNS解析失败
- 服务器错误
- 证书验证失败
### 安装错误
- 权限被拒绝
- APK文件损坏
- 存储空间不足
- 系统版本不兼容
### 自动重试
- 智能检测安装状态
- 多种重试方式
- 用户友好的错误提示
## 📊 最佳实践
### 1. 应用启动检查
```dart
// 推荐:应用启动时静默检查
WidgetsBinding.instance.addPostFrameCallback((_) {
AppUpgradeSimple.instance.checkUpdateSilent(
url: 'https://api.example.com/check-update',
).then((info) {
if (info != null && info.hasUpdate) {
// 发现更新,显示提示
showUpdateNotification(context, info);
}
});
});
```
### 2. 定期检查
```dart
// 推荐:每日检查一次
Timer.periodic(const Duration(days: 1), (_) {
AppUpgradeSimple.instance.checkUpdateSilent(
url: 'https://api.example.com/check-update',
);
});
```
### 3. 用户手动检查
```dart
// 推荐:提供手动检查按钮
ElevatedButton(
onPressed: () {
AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
);
},
child: const Text('检查更新'),
)
```
## 🎯 完整示例
查看 `example/lib/main_enhanced.dart` 获取完整的使用示例,包含所有功能的演示。
## 📝 API文档
### 主要方法
#### `checkUpdate()`
检查并处理应用更新的主要方法。
#### `checkUpdateSilent()`
静默检查更新不显示UI。
#### `preDownloadApk()`
后台预下载APK文件。
#### `clearDownloadCache()`
清理下载缓存。
#### `checkNetworkStatus()`
检查网络连接状态。
### 配置类
#### `UpgradeConfig`
升级配置类,控制插件的行为。
#### 预设配置
- `UpgradeConfig.auto` - 自动更新
- `UpgradeConfig.silent` - 静默检查
- `UpgradeConfig.development` - 开发模式
- `UpgradeConfig.production` - 生产模式
---
这个插件现在提供了完整的应用升级解决方案,支持各种使用场景和自定义需求!

View File

@ -1,738 +1,335 @@
import 'dart:async'; import 'package:app_upgrade_plugin/app_upgrade_plugin.dart';
import 'package:app_upgrade_plugin/app_upgrade_plugin_enhanced.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() { void main() {
runApp(const MyEnhancedApp()); runApp(const MyApp());
} }
class MyEnhancedApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyEnhancedApp({super.key}); const MyApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: '增强版App升级插件示例', title: 'App Upgrade Plugin Enhanced Demo',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, useMaterial3: true,
), ),
home: const EnhancedUpgradePage(), home: const MyHomePage(title: 'App升级插件完整示例'),
); );
} }
} }
class EnhancedUpgradePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const EnhancedUpgradePage({super.key}); const MyHomePage({super.key, required this.title});
final String title;
@override @override
State<EnhancedUpgradePage> createState() => _EnhancedUpgradePageState(); State<MyHomePage> createState() => _MyHomePageState();
} }
class _EnhancedUpgradePageState extends State<EnhancedUpgradePage> with SingleTickerProviderStateMixin { class _MyHomePageState extends State<MyHomePage> {
final _plugin = AppUpgradePluginEnhanced.instance; String _status = '准备就绪';
bool _isLoading = false;
late TabController _tabController;
//
Map<String, String>? _appInfo;
NetworkStatus? _networkStatus;
DownloadTask? _currentDownload;
UpgradeInfo? _upgradeInfo;
Map<String, dynamic>? _cacheStats;
//
bool _wifiOnly = true;
bool _autoCheck = true;
bool _supportBreakpoint = true;
bool _verifyIntegrity = true;
VersionCompareStrategy _versionStrategy = VersionCompareStrategy.semantic;
//
final _testVersions = ['1.0.0', '1.1.0', '1.2.0', '2.0.0-beta.1', '2.0.0'];
String _currentTestVersion = '1.0.0';
String _remoteTestVersion = '2.0.0';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: 4, vsync: this); _initializePlugin();
_initPlugin();
_loadData();
} }
@override ///
void dispose() { void _initializePlugin() {
_tabController.dispose();
_plugin.removeUpgradeCallback(_onUpgradeInfo);
_plugin.removeDownloadCallback(_onDownloadProgress);
_plugin.removeErrorCallback(_onError);
super.dispose();
}
void _initPlugin() {
// //
_plugin.configure( AppUpgradeSimple.instance.configure(const UpgradeConfig(
debugMode: true, enableDebugLog: true,
autoCheck: _autoCheck, installTimeout: 60,
wifiOnly: _wifiOnly, customToast: null, // 使Toast
supportBreakpoint: _supportBreakpoint, ));
verifyIntegrity: _verifyIntegrity, }
versionStrategy: _versionStrategy,
///
Future<void> _checkUpdate() async {
setState(() {
_isLoading = true;
_status = '检查更新中...';
});
try {
await AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
params: {
'platform': 'android',
'channel': 'official',
},
onComplete: () {
setState(() {
_isLoading = false;
_status = '检查完成';
});
},
);
} catch (e) {
setState(() {
_isLoading = false;
_status = '检查失败: $e';
});
}
}
///
Future<void> _autoUpdate() async {
setState(() {
_isLoading = true;
_status = '自动更新中...';
});
try {
await AppUpgradeSimple.instance.checkUpdate(
context: context,
url: 'https://api.example.com/check-update',
config: UpgradeConfig.auto,
onComplete: () {
setState(() {
_isLoading = false;
_status = '自动更新完成';
});
},
);
} catch (e) {
setState(() {
_isLoading = false;
_status = '自动更新失败: $e';
});
}
}
///
Future<void> _silentCheck() async {
setState(() {
_isLoading = true;
_status = '静默检查中...';
});
final info = await AppUpgradeSimple.instance.checkUpdateSilent(
url: 'https://api.example.com/check-update',
); );
//
_plugin.addUpgradeCallback(_onUpgradeInfo);
_plugin.addDownloadCallback(_onDownloadProgress);
_plugin.addErrorCallback(_onError);
//
NetworkMonitor.instance.statusStream.listen((status) {
setState(() { setState(() {
_networkStatus = status; _isLoading = false;
}); if (info != null && info.hasUpdate) {
_status = '发现新版本: ${info.versionName}';
} else {
_status = '已是最新版本';
}
}); });
} }
Future<void> _loadData() async { ///
// Future<void> _clearCache() async {
await _requestPermissions();
// App信息
_appInfo = await _plugin.getAppInfo();
//
_networkStatus = _plugin.networkStatus;
//
_cacheStats = await _plugin.getCacheStats();
setState(() {});
}
Future<void> _requestPermissions() async {
if (Theme.of(context).platform == TargetPlatform.android) {
await [
Permission.storage,
Permission.requestInstallPackages,
Permission.notification,
].request();
}
}
void _onUpgradeInfo(UpgradeInfo info) {
setState(() { setState(() {
_upgradeInfo = info; _isLoading = true;
_status = '清理缓存中...';
}); });
// await AppUpgradeSimple.instance.clearDownloadCache();
UpgradeDialog.show(
context,
upgradeInfo: info,
primaryColor: Theme.of(context).colorScheme.primary,
);
}
void _onDownloadProgress(DownloadTask task) {
setState(() { setState(() {
_currentDownload = task; _isLoading = false;
_status = '缓存清理完成';
}); });
} }
void _onError(String error) { ///
ScaffoldMessenger.of(context).showSnackBar( Future<void> _checkNetwork() async {
SnackBar( setState(() {
content: Text(error), _isLoading = true;
backgroundColor: Colors.red, _status = '检查网络中...';
), });
);
final hasNetwork = await AppUpgradeSimple.instance.checkNetworkStatus();
setState(() {
_isLoading = false;
_status = hasNetwork ? '网络连接正常' : '网络连接异常';
});
}
///
Future<void> _getAppInfo() async {
setState(() {
_isLoading = true;
_status = '获取应用信息中...';
});
final appInfo = await AppUpgradeSimple.instance.getAppInfo();
setState(() {
_isLoading = false;
_status = '应用信息: ${appInfo['appName']} v${appInfo['version']}';
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('增强版App升级插件'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
bottom: TabBar( title: Text(widget.title),
controller: _tabController, elevation: 2,
tabs: const [
Tab(text: '基础功能'),
Tab(text: '高级配置'),
Tab(text: '网络监测'),
Tab(text: '版本管理'),
],
), ),
), body: SingleChildScrollView(
body: TabBarView(
controller: _tabController,
children: [
_buildBasicTab(),
_buildConfigTab(),
_buildNetworkTab(),
_buildVersionTab(),
],
),
);
}
//
Widget _buildBasicTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// App信息卡片 //
if (_appInfo != null)
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'App信息', '当前状态',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: Theme.of(context).textTheme.titleMedium,
), ),
const SizedBox(height: 12), const SizedBox(height: 8),
Text('应用名称: ${_appInfo!['appName']}'),
Text('包名: ${_appInfo!['packageName']}'),
Text('版本: ${_appInfo!['version']}'),
Text('构建号: ${_appInfo!['buildNumber']}'),
],
),
),
),
const SizedBox(height: 16),
//
if (_upgradeInfo != null)
Card(
color: Colors.amber.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row( Row(
children: [ children: [
const Icon(Icons.system_update, color: Colors.amber), if (_isLoading)
const SizedBox(width: 8), const SizedBox(
const Text( width: 16,
'发现新版本', height: 16,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), child: CircularProgressIndicator(strokeWidth: 2),
),
if (_isLoading) const SizedBox(width: 8),
Expanded(
child: Text(
_status,
style: Theme.of(context).textTheme.bodyMedium,
),
), ),
], ],
), ),
const SizedBox(height: 12),
Text('版本: ${_upgradeInfo!.versionName}'),
Text('强制更新: ${_upgradeInfo!.isForceUpdate ? "" : ""}'),
if (_upgradeInfo!.apkSize != null) Text('大小: ${_formatBytes(_upgradeInfo!.apkSize!)}'),
], ],
), ),
), ),
), ),
const SizedBox(height: 20),
const SizedBox(height: 16), //
//
if (_currentDownload != null)
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'下载进度',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
LinearProgressIndicator(value: _currentDownload!.progress),
const SizedBox(height: 8),
Text('状态: ${_getDownloadStatusText(_currentDownload!.status)}'),
Text('进度: ${(_currentDownload!.progress * 100).toStringAsFixed(1)}%'),
if (_currentDownload!.totalSize != null)
Text( Text(
'${_formatBytes(_currentDownload!.downloadedSize)} / ${_formatBytes(_currentDownload!.totalSize!)}'), '基础功能',
if (_currentDownload!.errorMessage != null) style: Theme.of(context).textTheme.titleLarge,
Text('错误: ${_currentDownload!.errorMessage}', style: const TextStyle(color: Colors.red)),
],
), ),
), const SizedBox(height: 12),
),
const SizedBox(height: 16),
//
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _checkUpdate(), onPressed: _isLoading ? null : _checkUpdate,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.system_update),
label: const Text('检查更新'), label: const Text('检查更新'),
), ),
if (_currentDownload != null) ...[ const SizedBox(height: 8),
if (_currentDownload!.status == DownloadStatus.downloading)
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _plugin.pauseDownload(), onPressed: _isLoading ? null : _autoUpdate,
icon: const Icon(Icons.pause), icon: const Icon(Icons.auto_awesome),
label: const Text('暂停'), label: const Text('自动更新'),
), ),
if (_currentDownload!.status == DownloadStatus.paused) const SizedBox(height: 8),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _plugin.resumeDownload(), onPressed: _isLoading ? null : _silentCheck,
icon: const Icon(Icons.play_arrow), icon: const Icon(Icons.visibility_off),
label: const Text('继续'), label: const Text('静默检查'),
), ),
if (_currentDownload!.status == DownloadStatus.failed) const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () => _plugin.retryDownload(),
icon: const Icon(Icons.replay),
label: const Text('重试'),
),
OutlinedButton.icon(
onPressed: () => _plugin.cancelDownload(),
icon: const Icon(Icons.cancel),
label: const Text('取消'),
),
],
],
),
],
),
);
}
// //
Widget _buildConfigTab() { Text(
return SingleChildScrollView( '工具功能',
padding: const EdgeInsets.all(16), style: Theme.of(context).textTheme.titleLarge,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'升级配置',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('仅WiFi下载'),
subtitle: const Text('只在WiFi环境下自动下载更新'),
value: _wifiOnly,
onChanged: (value) {
setState(() {
_wifiOnly = value;
});
_plugin.configure(wifiOnly: value);
},
),
SwitchListTile(
title: const Text('自动检查更新'),
subtitle: const Text('定期自动检查是否有新版本'),
value: _autoCheck,
onChanged: (value) {
setState(() {
_autoCheck = value;
});
_plugin.configure(autoCheck: value);
},
),
SwitchListTile(
title: const Text('断点续传'),
subtitle: const Text('支持暂停后继续下载'),
value: _supportBreakpoint,
onChanged: (value) {
setState(() {
_supportBreakpoint = value;
});
_plugin.configure(supportBreakpoint: value);
},
),
SwitchListTile(
title: const Text('文件校验'),
subtitle: const Text('下载完成后校验文件完整性'),
value: _verifyIntegrity,
onChanged: (value) {
setState(() {
_verifyIntegrity = value;
});
_plugin.configure(verifyIntegrity: value);
},
),
const Divider(),
ListTile(
title: const Text('版本比较策略'),
subtitle: Text(_getVersionStrategyText(_versionStrategy)),
trailing: DropdownButton<VersionCompareStrategy>(
value: _versionStrategy,
onChanged: (value) {
if (value != null) {
setState(() {
_versionStrategy = value;
});
_plugin.configure(versionStrategy: value);
}
},
items: VersionCompareStrategy.values.map((strategy) {
return DropdownMenuItem(
value: strategy,
child: Text(_getVersionStrategyText(strategy)),
);
}).toList(),
),
),
const Divider(),
//
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'缓存管理',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (_cacheStats != null) ...[ ElevatedButton.icon(
Text( onPressed: _isLoading ? null : _checkNetwork,
'内存缓存: ${_cacheStats!['memoryCache']['sizeFormatted']} (${_cacheStats!['memoryCache']['count']}项)'), icon: const Icon(Icons.wifi),
Text( label: const Text('检查网络'),
'磁盘缓存: ${_cacheStats!['diskCache']['sizeFormatted']} (${_cacheStats!['diskCache']['count']}项)'),
Text('总计: ${_cacheStats!['total']['sizeFormatted']} (${_cacheStats!['total']['count']}项)'),
],
const SizedBox(height: 12),
Row(
children: [
ElevatedButton(
onPressed: () async {
_cacheStats = await _plugin.getCacheStats();
setState(() {});
},
child: const Text('刷新'),
), ),
const SizedBox(width: 8), const SizedBox(height: 8),
OutlinedButton( ElevatedButton.icon(
onPressed: () async { onPressed: _isLoading ? null : _getAppInfo,
await _plugin.clearCache(); icon: const Icon(Icons.info),
_cacheStats = await _plugin.getCacheStats(); label: const Text('应用信息'),
if (mounted) { ),
setState(() {}); const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _isLoading ? null : _clearCache,
icon: const Icon(Icons.cleaning_services),
label: const Text('清理缓存'),
),
const SizedBox(height: 20),
//
Text(
'配置示例',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _isLoading
? null
: () {
AppUpgradeSimple.instance.enableAutoUpdate();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('缓存已清空')), const SnackBar(content: Text('已启用自动更新模式')),
); );
}
}, },
child: const Text('清空缓存'), icon: const Icon(Icons.auto_mode),
label: const Text('启用自动更新'),
), ),
], const SizedBox(height: 8),
), ElevatedButton.icon(
], onPressed: _isLoading
), ? null
), : () {
), AppUpgradeSimple.instance.enableSilentCheck();
],
),
);
}
//
Widget _buildNetworkTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'网络状态',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
if (_networkStatus != null) ...[
Card(
color: _networkStatus!.isConnected ? Colors.green.shade50 : Colors.red.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
_networkStatus!.isConnected ? Icons.wifi : Icons.wifi_off,
color: _networkStatus!.isConnected ? Colors.green : Colors.red,
),
const SizedBox(width: 8),
Text(
_networkStatus!.isConnected ? '已连接' : '未连接',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 12),
Text('网络类型: ${_getNetworkTypeText(_networkStatus!.type)}'),
Text('网络质量: ${_getNetworkQualityText(_networkStatus!.quality)}'),
Text('计费网络: ${_networkStatus!.isMetered ? "" : ""}'),
if (_networkStatus!.downloadSpeed != null)
Text('下载速度: ${_formatBytes(_networkStatus!.downloadSpeed!.toInt())}/s'),
if (_networkStatus!.ping != null) Text('延迟: ${_networkStatus!.ping} ms'),
const SizedBox(height: 12),
Text(
'适合大文件下载: ${_networkStatus!.isSuitableForLargeDownload ? "" : ""}',
style: TextStyle(
color: _networkStatus!.isSuitableForLargeDownload ? Colors.green : Colors.orange,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
const SizedBox(height: 16),
//
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'下载策略建议',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
...NetworkMonitor.instance.getSuggestedDownloadStrategy().entries.map((e) {
return Text('${e.key}: ${e.value}');
}),
],
),
),
),
] else
const Center(child: CircularProgressIndicator()),
],
),
);
}
//
Widget _buildVersionTab() {
final comparator = _plugin.versionComparator;
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'版本比较测试',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
//
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('当前版本:'),
DropdownButton<String>(
value: _currentTestVersion,
isExpanded: true,
onChanged: (value) {
setState(() {
_currentTestVersion = value!;
});
},
items: _testVersions.map((v) {
return DropdownMenuItem(value: v, child: Text(v));
}).toList(),
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('远程版本:'),
DropdownButton<String>(
value: _remoteTestVersion,
isExpanded: true,
onChanged: (value) {
setState(() {
_remoteTestVersion = value!;
});
},
items: _testVersions.map((v) {
return DropdownMenuItem(value: v, child: Text(v));
}).toList(),
),
],
),
),
],
),
const SizedBox(height: 16),
//
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'比较结果',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text('需要更新: ${comparator.isUpdateAvailable(_currentTestVersion, _remoteTestVersion) ? "" : ""}'),
Text('更新类型: ${comparator.getVersionDifference(_currentTestVersion, _remoteTestVersion)}'),
Text('主要版本更新: ${comparator.isMajorUpdate(_currentTestVersion, _remoteTestVersion) ? "" : ""}'),
Text('次要版本更新: ${comparator.isMinorUpdate(_currentTestVersion, _remoteTestVersion) ? "" : ""}'),
Text('修订版本更新: ${comparator.isPatchUpdate(_currentTestVersion, _remoteTestVersion) ? "" : ""}'),
],
),
),
),
const SizedBox(height: 16),
//
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'版本排序',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text('升序: ${comparator.sortVersions(_testVersions).join(', ')}'),
Text('降序: ${comparator.sortVersions(_testVersions, descending: true).join(', ')}'),
Text('最新版本: ${comparator.getLatestVersion(_testVersions)}'),
],
),
),
),
],
),
);
}
//
Future<void> _checkUpdate() async {
//
const url = 'https://api.example.com/check-update';
final info = await _plugin.checkUpdateSmart(
url,
forceRefresh: true,
cacheDuration: const Duration(hours: 1),
);
if (info == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已是最新版本')), const SnackBar(content: Text('已启用静默检查模式')),
);
},
icon: const Icon(Icons.volume_off),
label: const Text('启用静默模式'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _isLoading
? null
: () {
AppUpgradeSimple.instance.configure(UpgradeConfig(
autoDownload: true,
autoInstall: false,
installTimeout: 60,
customToast: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.deepPurple,
),
);
},
));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已配置自定义设置')),
);
},
icon: const Icon(Icons.settings),
label: const Text('自定义配置'),
),
],
),
),
); );
}
}
//
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';
}
String _getDownloadStatusText(DownloadStatus status) {
switch (status) {
case DownloadStatus.pending:
return '等待中';
case DownloadStatus.downloading:
return '下载中';
case DownloadStatus.paused:
return '已暂停';
case DownloadStatus.completed:
return '已完成';
case DownloadStatus.failed:
return '失败';
case DownloadStatus.cancelled:
return '已取消';
}
}
String _getNetworkTypeText(NetworkType type) {
switch (type) {
case NetworkType.none:
return '无网络';
case NetworkType.mobile:
return '移动网络';
case NetworkType.wifi:
return 'WiFi';
case NetworkType.ethernet:
return '以太网';
case NetworkType.bluetooth:
return '蓝牙';
case NetworkType.vpn:
return 'VPN';
case NetworkType.other:
return '其他';
}
}
String _getNetworkQualityText(NetworkQuality quality) {
switch (quality) {
case NetworkQuality.unknown:
return '未知';
case NetworkQuality.poor:
return '';
case NetworkQuality.moderate:
return '中等';
case NetworkQuality.good:
return '良好';
case NetworkQuality.excellent:
return '优秀';
}
}
String _getVersionStrategyText(VersionCompareStrategy strategy) {
switch (strategy) {
case VersionCompareStrategy.numeric:
return '数字比较';
case VersionCompareStrategy.semantic:
return '语义化版本';
case VersionCompareStrategy.timestamp:
return '时间戳';
case VersionCompareStrategy.buildNumber:
return '构建号';
case VersionCompareStrategy.custom:
return '自定义';
}
} }
} }

View File

@ -37,10 +37,146 @@ class _SimpleAppUpgradePlugin {
Future<Map<String, String>> getAppInfo() { Future<Map<String, String>> getAppInfo() {
return AppUpgradePluginPlatform.instance.getAppInfo(); return AppUpgradePluginPlatform.instance.getAppInfo();
} }
///
Future<Map<String, String>> getCurrentAppInfo() async {
try {
return await AppUpgradePluginPlatform.instance.getAppInfo();
} catch (e) {
debugPrint('获取应用信息失败: $e');
return {};
}
}
///
Future<String?> getDownloadPath() {
return AppUpgradePluginPlatform.instance.getDownloadPath();
}
///
Future<bool> isPackageInstalled(String packageName) async {
try {
//
final appInfo = await getAppInfo();
final currentPackage = appInfo['packageName'] ?? '';
debugPrint('检查包安装状态: 当前包名=$currentPackage, 目标包名=$packageName');
//
if (currentPackage == packageName) {
return true; //
}
return false;
} catch (e) {
debugPrint('检查包安装状态失败: $e');
return false;
}
}
///
Future<bool> isVersionUpdated(String targetVersion, int? targetBuildNumber) async {
try {
final appInfo = await getCurrentAppInfo();
final currentVersion = appInfo['version'] ?? '';
final currentBuildNumber = int.tryParse(appInfo['buildNumber'] ?? '0') ?? 0;
debugPrint('版本对比: 当前版本=$currentVersion, 目标版本=$targetVersion');
debugPrint('构建号对比: 当前构建号=$currentBuildNumber, 目标构建号=$targetBuildNumber');
//
if (targetBuildNumber != null && targetBuildNumber > 0) {
return currentBuildNumber >= targetBuildNumber;
}
//
return currentVersion == targetVersion;
} catch (e) {
debugPrint('版本对比失败: $e');
return false;
}
}
}
///
class UpgradeConfig {
///
final bool showNoUpdateToast;
///
final bool autoDownload;
///
final bool autoInstall;
///
final int connectionTimeout;
///
final int downloadTimeout;
///
final int installTimeout;
///
final bool enableDebugLog;
/// Toast显示函数
final void Function(String message)? customToast;
const UpgradeConfig({
this.showNoUpdateToast = true,
this.autoDownload = false,
this.autoInstall = false,
this.connectionTimeout = 30,
this.downloadTimeout = 300,
this.installTimeout = 45,
this.enableDebugLog = true,
this.customToast,
});
///
static const UpgradeConfig auto = UpgradeConfig(
autoDownload: true,
autoInstall: true,
);
///
static const UpgradeConfig silent = UpgradeConfig(
showNoUpdateToast: false,
enableDebugLog: false,
);
/// +
static const UpgradeConfig development = UpgradeConfig(
enableDebugLog: true,
installTimeout: 30,
connectionTimeout: 10,
);
/// +
static const UpgradeConfig production = UpgradeConfig(
showNoUpdateToast: false,
enableDebugLog: false,
installTimeout: 60,
connectionTimeout: 30,
);
} }
/// App升级管理器 /// App升级管理器
/// APIApp升级功能 /// APIApp升级功能
///
/// : 2.0.0
/// : 2025-09-18
///
/// :
/// - 🎨 UI设计Material Design 3
/// - 🔄
/// - 📱
/// - 使
/// - 🎯
/// - 🚀
/// - 🛡
class AppUpgradeSimple { class AppUpgradeSimple {
static AppUpgradeSimple? _instance; static AppUpgradeSimple? _instance;
@ -61,10 +197,60 @@ class AppUpgradeSimple {
AppUpgradeSimple._() : _plugin = _SimpleAppUpgradePlugin.instance; AppUpgradeSimple._() : _plugin = _SimpleAppUpgradePlugin.instance;
final _SimpleAppUpgradePlugin _plugin; final _SimpleAppUpgradePlugin _plugin;
UpgradeConfig _config = const UpgradeConfig();
///
void configure(UpgradeConfig config) {
_config = config;
}
///
void enableAutoUpdate() {
_config = UpgradeConfig.auto;
}
///
void enableSilentCheck() {
_config = UpgradeConfig.silent;
}
///
Future<bool> checkNetworkStatus() async {
try {
final result = await InternetAddress.lookup('google.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (e) {
debugPrint('网络检查失败: $e');
return false;
}
}
///
Future<void> clearDownloadCache() async {
try {
final downloadPath = await _plugin.getDownloadPath();
if (downloadPath != null) {
final dir = Directory(downloadPath);
if (await dir.exists()) {
final files = await dir.list().where((file) => file.path.endsWith('.apk')).toList();
for (final file in files) {
try {
await file.delete();
debugPrint('已删除缓存文件: ${file.path}');
} catch (e) {
debugPrint('删除缓存文件失败: ${file.path}, 错误: $e');
}
}
}
}
} catch (e) {
debugPrint('清理下载缓存失败: $e');
}
}
/// 使 /// 使
/// ///
/// ///
/// ```dart /// ```dart
/// AppUpgradeSimple.instance.checkUpdate( /// AppUpgradeSimple.instance.checkUpdate(
/// context: context, /// context: context,
@ -72,25 +258,64 @@ class AppUpgradeSimple {
/// ); /// );
/// ``` /// ```
/// ///
///
/// ```dart
/// //
/// AppUpgradeSimple.instance.configure(UpgradeConfig.auto);
///
/// // 使
/// AppUpgradeSimple.instance.configure(UpgradeConfig(
/// autoDownload: true,
/// autoInstall: false,
/// installTimeout: 60,
/// customToast: (message) => ScaffoldMessenger.of(context).showSnackBar(
/// SnackBar(content: Text(message)),
/// ),
/// ));
///
/// //
/// await AppUpgradeSimple.instance.checkUpdate(
/// context: context,
/// url: 'https://api.example.com/check-update',
/// params: {'userId': '123', 'channel': 'official'},
/// onComplete: () => print('更新检查完成'),
/// );
/// ```
///
/// [context] 访 `Navigator` `MaterialLocalizations` /// [context] 访 `Navigator` `MaterialLocalizations`
/// `init` `navigatorKey` /// [url] API接口地址
/// [params]
/// [config]
/// [onComplete]
///
Future<void> checkUpdate({ Future<void> checkUpdate({
required BuildContext context, required BuildContext context,
required String url, required String url,
Map<String, dynamic>? params, Map<String, dynamic>? params,
bool showNoUpdateToast = true, bool? showNoUpdateToast,
bool autoDownload = false, bool? autoDownload,
bool autoInstall = false, bool? autoInstall,
VoidCallback? onComplete, VoidCallback? onComplete,
UpgradeConfig? config,
}) async { }) async {
// 使
final effectiveConfig = config ?? _config;
final finalShowNoUpdateToast = showNoUpdateToast ?? effectiveConfig.showNoUpdateToast;
final finalAutoDownload = autoDownload ?? effectiveConfig.autoDownload;
final finalAutoInstall = autoInstall ?? effectiveConfig.autoInstall;
try { try {
assert(_canShowMaterialDialog(context), '请在 MaterialApp 环境内调用'); assert(_canShowMaterialDialog(context), '请在 MaterialApp 环境内调用');
// //
final info = await _plugin.checkUpdate(url, params: params); final info = await _plugin.checkUpdate(url, params: params);
print('info: $info'); if (effectiveConfig.enableDebugLog) {
debugPrint('🔍 检查更新结果: $info');
}
if (info == null || !info.hasUpdate) { if (info == null || !info.hasUpdate) {
if (showNoUpdateToast && context.mounted) _showToast('已是最新版本'); if (finalShowNoUpdateToast && context.mounted) {
_showToast('已是最新版本', effectiveConfig);
}
onComplete?.call(); onComplete?.call();
return; return;
} }
@ -98,9 +323,10 @@ class AppUpgradeSimple {
await _showUpgradeDialog( await _showUpgradeDialog(
context: context, context: context,
info: info, info: info,
autoDownload: autoDownload, autoDownload: finalAutoDownload,
autoInstall: autoInstall, autoInstall: finalAutoInstall,
onComplete: onComplete, onComplete: onComplete,
config: effectiveConfig,
); );
} catch (e) { } catch (e) {
debugPrint('检查更新失败: $e'); debugPrint('检查更新失败: $e');
@ -121,11 +347,13 @@ class AppUpgradeSimple {
} }
if (context.mounted) { if (context.mounted) {
_showToast(errorMessage); _showToast(errorMessage, effectiveConfig);
// //
if (errorString.contains('Failed host lookup') || errorString.contains('无网络连接')) { if (errorString.contains('Failed host lookup') || errorString.contains('无网络连接')) {
debugPrint('建议: 请检查网络连接或尝试使用网络诊断功能'); if (effectiveConfig.enableDebugLog) {
debugPrint('💡 建议: 请检查网络连接或尝试使用网络诊断功能');
}
} }
} }
onComplete?.call(); onComplete?.call();
@ -140,11 +368,60 @@ class AppUpgradeSimple {
try { try {
return await _plugin.checkUpdate(url, params: params); return await _plugin.checkUpdate(url, params: params);
} catch (e) { } catch (e) {
if (_config.enableDebugLog) {
debugPrint('静默检查更新失败: $e'); debugPrint('静默检查更新失败: $e');
}
return null; return null;
} }
} }
/// APKUI
Future<String?> preDownloadApk({
required String url,
Function(DownloadProgress)? onProgress,
}) async {
try {
return await _plugin.downloadApk(url, onProgress: onProgress);
} catch (e) {
if (_config.enableDebugLog) {
debugPrint('预下载APK失败: $e');
}
return null;
}
}
/// APK文件
Future<String?> findDownloadedApk(String version) async {
try {
final downloadPath = await _plugin.getDownloadPath();
if (downloadPath != null) {
final dir = Directory(downloadPath);
if (await dir.exists()) {
final files = await dir.list().toList();
for (final file in files) {
if (file.path.contains(version) && file.path.endsWith('.apk')) {
final fileEntity = File(file.path);
if (await fileEntity.exists()) {
return file.path;
}
}
}
}
}
return null;
} catch (e) {
if (_config.enableDebugLog) {
debugPrint('查找已下载APK失败: $e');
}
return null;
}
}
///
Future<Map<String, String>> getAppInfo() async {
return await _plugin.getAppInfo();
}
/// ///
Future<void> _showUpgradeDialog({ Future<void> _showUpgradeDialog({
required BuildContext context, required BuildContext context,
@ -152,20 +429,27 @@ class AppUpgradeSimple {
required bool autoDownload, required bool autoDownload,
required bool autoInstall, required bool autoInstall,
VoidCallback? onComplete, VoidCallback? onComplete,
UpgradeConfig? config,
}) { }) {
final effectiveConfig = config ?? _config;
return showDialog( return showDialog(
context: context, context: context,
barrierDismissible: !info.isForceUpdate, barrierDismissible: !info.isForceUpdate,
builder: (context) { builder: (context) {
if (info.isForceUpdate) { if (info.isForceUpdate) {
return _ForceUpgradeDialog(info: info); return _ForceUpgradeDialog(
info: info,
config: effectiveConfig,
);
} else { } else {
return _SimpleUpgradeDialog( return _SimpleUpgradeDialog(
info: info, info: info,
autoDownload: autoDownload, autoDownload: autoDownload,
autoInstall: autoInstall, autoInstall: autoInstall,
onComplete: onComplete, onComplete: onComplete,
showToast: (message) => _showToast(message), config: effectiveConfig,
showToast: (message) => _showToast(message, effectiveConfig),
); );
} }
}, },
@ -173,8 +457,20 @@ class AppUpgradeSimple {
} }
/// Toast提示 /// Toast提示
void _showToast(String message) { void _showToast(String message, [UpgradeConfig? config]) {
Fluttertoast.showToast(msg: message); final effectiveConfig = config ?? _config;
if (effectiveConfig.customToast != null) {
effectiveConfig.customToast!(message);
} else {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 14.0,
);
}
} }
/// Material /// Material
@ -206,6 +502,7 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
VoidCallback? get onComplete; VoidCallback? get onComplete;
bool get autoDownload; bool get autoDownload;
bool get autoInstall; bool get autoInstall;
UpgradeConfig get config;
void initUpgradeLogic() { void initUpgradeLogic() {
if (autoDownload && Platform.isAndroid) { if (autoDownload && Platform.isAndroid) {
@ -222,13 +519,14 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
} }
void onAppLifecycleStateChanged(AppLifecycleState state) { void onAppLifecycleStateChanged(AppLifecycleState state) {
debugPrint('应用生命周期状态变化: $state, _isWaitingForInstallation=$_isWaitingForInstallation'); debugPrint('🔄 应用生命周期状态变化: $state, _isWaitingForInstallation=$_isWaitingForInstallation');
// //
if (state == AppLifecycleState.resumed && _isWaitingForInstallation) { if (_isWaitingForInstallation && state == AppLifecycleState.resumed) {
debugPrint('应用回到前台,准备检查安装状态'); debugPrint('⚡ 应用回到前台,检查安装状态');
//
Future.delayed(const Duration(milliseconds: 500), () { //
Future.delayed(const Duration(milliseconds: 1500), () {
if (mounted && _isWaitingForInstallation) { if (mounted && _isWaitingForInstallation) {
_checkInstallationResult(); _checkInstallationResult();
} }
@ -321,7 +619,7 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
_isWaitingForInstallation = true; _isWaitingForInstallation = true;
_statusText = '请完成安装'; _statusText = '请完成安装';
}); });
showToast('请在系统弹窗中完成安装,如取消可点击下方按钮重试'); showToast('请在系统弹窗中完成安装,完成后点击下方确认按钮');
// //
_startInstallationTimeoutCheck(); _startInstallationTimeoutCheck();
@ -345,47 +643,73 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
} }
} }
/// ///
void _startInstallationTimeoutCheck() { void _startInstallationTimeoutCheck() {
// 5 debugPrint('🚀 启动简化安装检测系统');
Timer(const Duration(seconds: 5), () {
if (mounted && _isWaitingForInstallation && _statusText == '请完成安装') {
debugPrint('5秒后自动检测安装状态');
_checkInstallationResult();
}
});
// 30 //
_installCheckTimer = Timer(const Duration(seconds: 30), () {
if (mounted && _isWaitingForInstallation && _statusText == '请完成安装') { //
_installCheckTimer = Timer(Duration(seconds: config.installTimeout), () {
if (mounted && _isWaitingForInstallation) {
debugPrint('⏰ 安装检测超时');
setState(() { setState(() {
_isWaitingForInstallation = false; _isWaitingForInstallation = false;
_statusText = '安装超时'; _statusText = '安装超时';
}); });
showToast('安装检测超时,请检查是否已安装成功'); showToast('安装超时,请使用重试按钮重新安装');
} }
}); });
} }
/// ///
Future<void> _checkInstallationResult() async { Future<void> _checkInstallationResult() async {
if (!mounted || !_isWaitingForInstallation) { if (!mounted || !_isWaitingForInstallation) {
debugPrint('跳过安装结果检查: mounted=$mounted, _isWaitingForInstallation=$_isWaitingForInstallation'); debugPrint('跳过安装结果检查: mounted=$mounted, _isWaitingForInstallation=$_isWaitingForInstallation');
return; return;
} }
debugPrint('开始检查安装结果...'); debugPrint('🔍 开始简化安装检测...');
try { try {
// //
final appInfo = await _plugin.getAppInfo(); final appInfo = await _plugin.getCurrentAppInfo();
final currentVersion = appInfo['versionName'] ?? ''; final currentVersion = appInfo['version'] ?? '';
final currentBuildNumber = int.tryParse(appInfo['buildNumber'] ?? '0') ?? 0;
debugPrint('检查安装结果: 当前版本=$currentVersion, 目标版本=${info.versionName}'); debugPrint('📱 当前版本: $currentVersion, 构建号: $currentBuildNumber');
debugPrint('🎯 目标版本: ${info.versionName}, 构建号: ${info.versionBuildNumber}');
// //
if (currentVersion == info.versionName) { bool isUpdated = false;
debugPrint('检测到版本已更新,安装成功'); if (info.versionBuildNumber > 0) {
isUpdated = currentBuildNumber >= info.versionBuildNumber;
debugPrint('📊 构建号比较: $currentBuildNumber >= ${info.versionBuildNumber} = $isUpdated');
} else {
//
isUpdated = currentVersion == info.versionName;
debugPrint('📊 版本号比较: $currentVersion == ${info.versionName} = $isUpdated');
}
if (isUpdated) {
debugPrint('✅ 检测结果: 安装成功');
_handleInstallationSuccess();
} else {
debugPrint('❌ 检测结果: 安装被取消(版本未更新)');
_handleInstallationCancelled();
}
} catch (e) {
debugPrint('❌ 检测失败: $e');
setState(() {
_isWaitingForInstallation = false;
_statusText = '检测失败';
});
showToast('无法检测安装状态,请手动确认');
}
}
///
void _handleInstallationSuccess() {
_installCheckTimer?.cancel(); _installCheckTimer?.cancel();
setState(() { setState(() {
_isWaitingForInstallation = false; _isWaitingForInstallation = false;
@ -400,23 +724,15 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
} }
onComplete?.call(); onComplete?.call();
}); });
} else { }
//
debugPrint('版本号未更新,用户可能取消了安装'); ///
void _handleInstallationCancelled() {
setState(() { setState(() {
_isWaitingForInstallation = false; _isWaitingForInstallation = false;
_statusText = '安装被取消'; _statusText = '安装被取消';
}); });
showToast('安装被取消,可以重新尝试安装'); showToast('安装被取消,可以点击重试按钮重新安装');
}
} catch (e) {
debugPrint('检测应用状态失败: $e');
setState(() {
_isWaitingForInstallation = false;
_statusText = '检测失败';
});
showToast('无法检测安装状态,请手动检查');
}
} }
/// APK /// APK
@ -842,31 +1158,40 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
// //
Row( Row(
children: [ children: [
Container( AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getStatusColor(colorScheme).withOpacity(0.1), color: _getStatusColor(colorScheme).withOpacity(0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Icon( child: Icon(
_getStatusIcon(), _getStatusIcon(),
key: ValueKey(_statusText),
color: _getStatusColor(colorScheme), color: _getStatusColor(colorScheme),
size: 20, size: 20,
), ),
), ),
),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
_statusText, _statusText,
key: ValueKey(_statusText),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: colorScheme.onSurface, color: colorScheme.onSurface,
), ),
), ),
),
const SizedBox(height: 2), const SizedBox(height: 2),
if (_isDownloading || _downloadProgress < 1.0) if (_isDownloading || _downloadProgress < 1.0)
Text( Text(
@ -878,83 +1203,58 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
), ),
), ),
// //
if (showRetryButton && if (_shouldShowRetryOptions())
(_statusText == '安装失败' ||
_statusText == '安装异常' ||
_statusText == '权限被拒绝' ||
_statusText == '请完成安装' ||
_statusText == '安装超时' ||
_statusText == '安装被取消' ||
_statusText == '检测失败'))
Text( Text(
_statusText == '请完成安装' ? '可重新启动安装' : '点击区域重试', _getClickHintText(),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: colorScheme.onSurface.withOpacity(0.6), color: colorScheme.onSurface.withOpacity(0.6),
), ),
), ),
// "请完成安装" // "请完成安装"
if (_statusText == '请完成安装') if (_statusText == '请完成安装')
Padding( Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 12),
child: Row( child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 1,
),
),
child: Column(
children: [ children: [
Expanded( Icon(
child: ElevatedButton.icon( Icons.touch_app,
onPressed: _checkInstallationResult, color: colorScheme.primary,
icon: const Icon(Icons.refresh, size: 14), size: 24,
label: const Text('检查状态', style: TextStyle(fontSize: 11)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: colorScheme.secondary.withOpacity(0.8),
), ),
const SizedBox(height: 8),
Text(
'请在系统安装界面完成操作',
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface,
fontWeight: FontWeight.w500,
), ),
textAlign: TextAlign.center,
), ),
const SizedBox(width: 4), const SizedBox(height: 4),
Expanded( Text(
child: ElevatedButton.icon( '系统将自动检测安装结果',
onPressed: () async { style: TextStyle(
// fontSize: 11,
if (_downloadedFilePath != null) { color: colorScheme.onSurface.withOpacity(0.7),
await _installApk(_downloadedFilePath!);
}
},
icon: const Icon(Icons.install_mobile, size: 14),
label: const Text('重新安装', style: TextStyle(fontSize: 11)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: colorScheme.primary,
),
),
),
const SizedBox(width: 4),
//
Expanded(
child: ElevatedButton.icon(
onPressed: () {
setState(() {
_isWaitingForInstallation = false;
_statusText = '安装被取消';
});
showToast('已模拟安装被取消,现在应该显示重试按钮');
},
icon: const Icon(Icons.cancel, size: 14),
label: const Text('模拟取消', style: TextStyle(fontSize: 10)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: colorScheme.error.withOpacity(0.8),
),
), ),
textAlign: TextAlign.center,
), ),
], ],
), ),
), ),
),
], ],
), ),
), ),
@ -1046,6 +1346,12 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
return Icons.check_circle; return Icons.check_circle;
} else if (_statusText == '请完成安装') { } else if (_statusText == '请完成安装') {
return Icons.touch_app; return Icons.touch_app;
} else if (_statusText == '等待安装中') {
return Icons.hourglass_bottom;
} else if (_statusText == '等待确认中') {
return Icons.help_outline;
} else if (_statusText == '等待超时') {
return Icons.timer_off;
} else if (_statusText == '安装被取消') { } else if (_statusText == '安装被取消') {
return Icons.cancel_outlined; return Icons.cancel_outlined;
} else if (_statusText == '安装超时' || _statusText == '检测失败') { } else if (_statusText == '安装超时' || _statusText == '检测失败') {
@ -1067,6 +1373,12 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
return colorScheme.tertiary; return colorScheme.tertiary;
} else if (_statusText == '请完成安装') { } else if (_statusText == '请完成安装') {
return colorScheme.secondary; return colorScheme.secondary;
} else if (_statusText == '等待安装中') {
return colorScheme.secondary.withOpacity(0.9);
} else if (_statusText == '等待确认中') {
return Colors.orange;
} else if (_statusText == '等待超时') {
return colorScheme.error.withOpacity(0.7);
} else if (_statusText == '安装被取消') { } else if (_statusText == '安装被取消') {
return colorScheme.secondary.withOpacity(0.8); return colorScheme.secondary.withOpacity(0.8);
} else if (_statusText == '安装超时' || _statusText == '检测失败') { } else if (_statusText == '安装超时' || _statusText == '检测失败') {
@ -1088,7 +1400,10 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
_statusText == '安装异常' || _statusText == '安装异常' ||
_statusText == '安装超时' || _statusText == '安装超时' ||
_statusText == '安装被取消' || _statusText == '安装被取消' ||
_statusText == '检测失败') { _statusText == '检测失败' ||
_statusText == '等待安装中' ||
_statusText == '等待确认中' ||
_statusText == '等待超时') {
return Icons.refresh; return Icons.refresh;
} else if (_statusText == '请完成安装') { } else if (_statusText == '请完成安装') {
return Icons.launch; return Icons.launch;
@ -1105,8 +1420,11 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
_statusText == '安装异常' || _statusText == '安装异常' ||
_statusText == '安装超时' || _statusText == '安装超时' ||
_statusText == '安装被取消' || _statusText == '安装被取消' ||
_statusText == '检测失败') { _statusText == '检测失败' ||
_statusText == '等待超时') {
return '重试'; return '重试';
} else if (_statusText == '等待安装中' || _statusText == '等待确认中') {
return '重新安装';
} else if (_statusText == '请完成安装') { } else if (_statusText == '请完成安装') {
return '重新安装'; return '重新安装';
} else { } else {
@ -1120,7 +1438,7 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
return colorScheme.secondary; return colorScheme.secondary;
} else if (_statusText == '安装失败' || _statusText == '安装异常') { } else if (_statusText == '安装失败' || _statusText == '安装异常') {
return colorScheme.error; return colorScheme.error;
} else if (_statusText == '安装超时' || _statusText == '安装被取消' || _statusText == '检测失败') { } else if (_statusText == '安装超时' || _statusText == '安装被取消' || _statusText == '检测失败' || _statusText == '等待安装中') {
return colorScheme.secondary.withOpacity(0.8); return colorScheme.secondary.withOpacity(0.8);
} else if (_statusText == '请完成安装') { } else if (_statusText == '请完成安装') {
return colorScheme.secondary; return colorScheme.secondary;
@ -1137,6 +1455,9 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
_statusText == '权限被拒绝' || _statusText == '权限被拒绝' ||
_statusText == '安装超时' || _statusText == '安装超时' ||
_statusText == '检测失败' || _statusText == '检测失败' ||
_statusText == '等待安装中' ||
_statusText == '等待确认中' ||
_statusText == '等待超时' ||
(_downloadedFilePath != null && (_downloadedFilePath != null &&
!_isDownloading && !_isDownloading &&
!_isInstalling && !_isInstalling &&
@ -1145,6 +1466,28 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
_statusText != '安装成功'); _statusText != '安装成功');
} }
///
String _getClickHintText() {
switch (_statusText) {
case '请完成安装':
return '完成后请点击确认按钮';
case '等待安装中':
return '点击重新安装';
case '等待确认中':
return '请点击确认按钮';
case '等待超时':
return '点击重新安装';
case '安装被取消':
return '点击重新安装';
case '权限被拒绝':
return '点击打开设置';
case '安装超时':
return '点击重新尝试';
default:
return '点击区域重试';
}
}
void _handleAction() { void _handleAction() {
if (Platform.isAndroid) { if (Platform.isAndroid) {
_handleAndroidAction(); _handleAndroidAction();
@ -1341,6 +1684,7 @@ class _SimpleUpgradeDialog extends StatefulWidget {
final bool autoInstall; final bool autoInstall;
final VoidCallback? onComplete; final VoidCallback? onComplete;
final void Function(String) showToast; final void Function(String) showToast;
final UpgradeConfig config;
const _SimpleUpgradeDialog({ const _SimpleUpgradeDialog({
required this.info, required this.info,
@ -1348,6 +1692,7 @@ class _SimpleUpgradeDialog extends StatefulWidget {
required this.autoInstall, required this.autoInstall,
this.onComplete, this.onComplete,
required this.showToast, required this.showToast,
required this.config,
}); });
@override @override
@ -1365,6 +1710,8 @@ class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _Upgrad
bool get autoDownload => widget.autoDownload; bool get autoDownload => widget.autoDownload;
@override @override
bool get autoInstall => widget.autoInstall; bool get autoInstall => widget.autoInstall;
@override
UpgradeConfig get config => widget.config;
@override @override
void initState() { void initState() {
@ -1477,8 +1824,12 @@ class _SimpleUpgradeDialogState extends State<_SimpleUpgradeDialog> with _Upgrad
/// ///
class _ForceUpgradeDialog extends StatefulWidget { class _ForceUpgradeDialog extends StatefulWidget {
final UpgradeInfo info; final UpgradeInfo info;
final UpgradeConfig config;
const _ForceUpgradeDialog({required this.info}); const _ForceUpgradeDialog({
required this.info,
required this.config,
});
@override @override
State<_ForceUpgradeDialog> createState() => _ForceUpgradeDialogState(); State<_ForceUpgradeDialog> createState() => _ForceUpgradeDialogState();
@ -1488,13 +1839,15 @@ class _ForceUpgradeDialogState extends State<_ForceUpgradeDialog> with _UpgradeD
@override @override
UpgradeInfo get info => widget.info; UpgradeInfo get info => widget.info;
@override @override
void Function(String) get showToast => (message) => AppUpgradeSimple.instance._showToast(message); void Function(String) get showToast => (message) => AppUpgradeSimple.instance._showToast(message, widget.config);
@override @override
VoidCallback? get onComplete => null; VoidCallback? get onComplete => null;
@override @override
bool get autoDownload => false; bool get autoDownload => false;
@override @override
bool get autoInstall => true; bool get autoInstall => true;
@override
UpgradeConfig get config => widget.config;
@override @override
void initState() { void initState() {