503 lines
16 KiB
Dart
503 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:package_info_plus/package_info_plus.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../services/update_service.dart';
|
||
|
||
class UpdateDialogService {
|
||
final UpdateService _updateService = UpdateService();
|
||
static final UpdateDialogService _instance = UpdateDialogService._internal();
|
||
bool _hasCheckedVersion = false; // 标记是否已在本次应用生命周期内检查过版本
|
||
bool _hasShownDialog = false; // 新增:标记本次APP打开期间是否已经显示过弹窗
|
||
bool isNeedUpdate(String currentVersion, String serverVersion) {
|
||
return _updateService.isNeedUpdate(currentVersion, serverVersion);
|
||
}
|
||
|
||
// 添加一个获取UpdateService的方法
|
||
UpdateService getUpdateService() {
|
||
return _updateService;
|
||
}
|
||
|
||
factory UpdateDialogService() {
|
||
return _instance;
|
||
}
|
||
|
||
UpdateDialogService._internal();
|
||
|
||
// 检查更新方法 (自动检查/手动检查共用)
|
||
Future<VersionInfo?> checkForUpdate({
|
||
bool showLoading = false,
|
||
bool forceCheck = false,
|
||
Function(bool)? updateCheckingState,
|
||
Function(String)? showMessage,
|
||
}) async {
|
||
try {
|
||
// 先获取当前应用版本信息进行比较,避免更新后仍然提示更新
|
||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||
String currentVersion = packageInfo.version;
|
||
|
||
if (showLoading && updateCheckingState != null) {
|
||
updateCheckingState(true);
|
||
}
|
||
|
||
final versionInfo = await _updateService.checkUpdate();
|
||
|
||
// 没有新版本或当前版本已经是最新版本
|
||
if (versionInfo == null ||
|
||
!_updateService.isNeedUpdate(currentVersion, versionInfo.version)) {
|
||
if (showMessage != null) {
|
||
showMessage('已是最新版本');
|
||
}
|
||
// 标记已经检查过版本,避免重复弹窗
|
||
_hasCheckedVersion = true;
|
||
_hasShownDialog = true;
|
||
return null;
|
||
}
|
||
|
||
// 标记本次APP打开期间已经检查过版本
|
||
_hasCheckedVersion = true;
|
||
// 标记将要显示弹窗
|
||
_hasShownDialog = true;
|
||
|
||
return versionInfo;
|
||
} catch (e) {
|
||
if (showMessage != null) {
|
||
showMessage('检查更新失败,请稍后重试');
|
||
}
|
||
print('版本检查失败: $e');
|
||
return null;
|
||
} finally {
|
||
if (showLoading && updateCheckingState != null) {
|
||
updateCheckingState(false);
|
||
}
|
||
}
|
||
}
|
||
// 重置所有检查状态(在APP完全启动或被杀死重启时调用)
|
||
void resetAllCheckStatus() async {
|
||
// 重置版本检查标记
|
||
_hasCheckedVersion = false;
|
||
_hasShownDialog = false;
|
||
|
||
try {
|
||
// 清除SharedPreferences中的检查记录
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.remove('last_checked_version');
|
||
await prefs.remove('last_check_time');
|
||
|
||
print('所有更新检查状态已重置');
|
||
} catch (e) {
|
||
print('重置更新检查状态时出错: $e');
|
||
}
|
||
}
|
||
|
||
// 重置版本检查状态(在切换页面时调用)
|
||
void resetVersionCheck() {
|
||
_hasCheckedVersion = false;
|
||
_hasShownDialog = false;
|
||
}
|
||
// 检查是否应该进行版本更新检查
|
||
Future<bool> shouldCheckUpdate() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
String? lastCheckedVersion = prefs.getString('last_checked_version');
|
||
final packageInfo = await PackageInfo.fromPlatform();
|
||
String currentVersion = packageInfo.version;
|
||
|
||
// 如果当前版本与上次检查版本不同,更新记录并返回true
|
||
if (lastCheckedVersion != currentVersion) {
|
||
await prefs.setString('last_checked_version', currentVersion);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 强制重启应用的方法
|
||
void restartApp(BuildContext context) {
|
||
print('正在重启应用以刷新版本信息...');
|
||
Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/', // 主路由
|
||
(route) => false, // 清除所有路由
|
||
);
|
||
}
|
||
|
||
// 在更新完成后调用
|
||
Future<void> onUpdateCompleted(BuildContext context) async {
|
||
// 延迟一段时间,确保安装完成
|
||
await Future.delayed(const Duration(seconds: 1));
|
||
restartApp(context);
|
||
}
|
||
|
||
|
||
|
||
// 显示更新对话框
|
||
void showUpdateDialog(BuildContext context, VersionInfo versionInfo) {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: !versionInfo.forceUpdate,
|
||
builder: (context) => Dialog(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
child: Container(
|
||
width: 380,
|
||
height: 354,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(20),
|
||
image: const DecorationImage(
|
||
image: AssetImage('images/version.png'),
|
||
fit: BoxFit.cover,
|
||
),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const SizedBox(height: 136),
|
||
// 第一行标题
|
||
const Center(
|
||
child: Text(
|
||
'叮咚!看那APP焕新升级至',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
// 第二行版本号
|
||
Center(
|
||
child: Text(
|
||
'快乐加载版 V${versionInfo.version}',
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 20),
|
||
// 更新内容 - 两行新文案
|
||
const Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 40),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'新功能已就位,老BUG已跑路~',
|
||
style: TextStyle(color: Color(0xff75757F), fontSize: 12),
|
||
textAlign: TextAlign.left,
|
||
),
|
||
SizedBox(height: 8),
|
||
Text(
|
||
'点击更新,解锁隐藏彩蛋',
|
||
style: TextStyle(color: Color(0xff75757F), fontSize: 12),
|
||
textAlign: TextAlign.left,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
// 按钮部分保持不变
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 立即更新按钮
|
||
GestureDetector(
|
||
onTap: () {
|
||
Navigator.pop(context);
|
||
startUpdate(context, versionInfo.downloadUrl);
|
||
},
|
||
child: Container(
|
||
width: 232,
|
||
height: 45,
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xFF80DAA4), Color(0xFF79DDED)],
|
||
begin: Alignment.centerLeft,
|
||
end: Alignment.centerRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Center(
|
||
child: Text(
|
||
'立即更新',
|
||
style: TextStyle(
|
||
color: Colors.black,
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (!versionInfo.forceUpdate)
|
||
GestureDetector(
|
||
onTap: () {
|
||
_hasCheckedVersion = true;
|
||
Navigator.pop(context);
|
||
},
|
||
child: Container(
|
||
width: 232,
|
||
height: 50,
|
||
child: Center(
|
||
child: ShaderMask(
|
||
shaderCallback: (Rect bounds) {
|
||
return const LinearGradient(
|
||
colors: [Color(0xFF80DAA4), Color(0xFF79DDED)],
|
||
begin: Alignment.centerLeft,
|
||
end: Alignment.centerRight,
|
||
).createShader(bounds);
|
||
},
|
||
child: const Text(
|
||
'稍后再说',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
// 开始更新
|
||
Future<void> startUpdate(BuildContext context, String downloadUrl) async {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) => _DownloadProgressDialog(
|
||
downloadUrl: downloadUrl,
|
||
updateService: _updateService,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
// 下载进度对话框
|
||
class _DownloadProgressDialog extends StatefulWidget {
|
||
final String downloadUrl;
|
||
final UpdateService updateService;
|
||
|
||
const _DownloadProgressDialog({
|
||
required this.downloadUrl,
|
||
required this.updateService,
|
||
});
|
||
|
||
@override
|
||
State<_DownloadProgressDialog> createState() =>
|
||
_DownloadProgressDialogState();
|
||
}
|
||
|
||
class _DownloadProgressDialogState extends State<_DownloadProgressDialog> {
|
||
double _progress = 0;
|
||
bool _isDownloading = true;
|
||
String _errorMessage = '';
|
||
bool _showRetry = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_startDownload();
|
||
}
|
||
|
||
Future<void> _startDownload() async {
|
||
try {
|
||
setState(() {
|
||
_isDownloading = true;
|
||
_errorMessage = '';
|
||
_showRetry = false;
|
||
});
|
||
|
||
final result = await widget.updateService.downloadAndInstall(
|
||
widget.downloadUrl,
|
||
(progress) {
|
||
setState(() {
|
||
_progress = progress;
|
||
});
|
||
},
|
||
);
|
||
|
||
setState(() {
|
||
_isDownloading = false;
|
||
});
|
||
|
||
if (!result.$1) {
|
||
setState(() {
|
||
_errorMessage = result.$2;
|
||
// 如果是权限问题,显示重试按钮
|
||
_showRetry = result.$2.contains('权限') ||
|
||
result.$2.contains('permission') ||
|
||
result.$2.contains('授予');
|
||
});
|
||
|
||
// 只有当不需要重试时才自动关闭
|
||
if (!_showRetry) {
|
||
Future.delayed(const Duration(seconds: 3), () {
|
||
if (mounted) {
|
||
Navigator.of(context).pop();
|
||
}
|
||
});
|
||
}
|
||
} else {
|
||
// 下载成功,关闭对话框
|
||
if (mounted) {
|
||
Navigator.of(context).pop();
|
||
}
|
||
}
|
||
} catch (e) {
|
||
setState(() {
|
||
_isDownloading = false;
|
||
_errorMessage = '下载失败: $e';
|
||
_showRetry = true;
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Dialog(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
child: Container(
|
||
width: 280,
|
||
height: _isDownloading || _errorMessage.isEmpty ? 144 : 200,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1F212E),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
padding: const EdgeInsets.all(20),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (_isDownloading) ...[
|
||
const Text(
|
||
'下载更新',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
// 优化进度条样式
|
||
ClipRRect(
|
||
borderRadius: BorderRadius.circular(4),
|
||
child: LinearProgressIndicator(
|
||
value: _progress,
|
||
backgroundColor: Colors.white.withOpacity(0.2),
|
||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||
Color(0xFF80DAA4), // 使用渐变色的起始色
|
||
),
|
||
minHeight: 8, // 增加进度条高度
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
'正在下载更新包...${(_progress * 100).toStringAsFixed(1)}%',
|
||
style: TextStyle(
|
||
color: Colors.white.withOpacity(0.5),
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'下载过程请保持该界面',
|
||
style: TextStyle(
|
||
color: Colors.white.withOpacity(0.3),
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
] else if (_errorMessage.isNotEmpty) ...[
|
||
const Icon(
|
||
Icons.error_outline,
|
||
color: Colors.red,
|
||
size: 48,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
_errorMessage.contains('下载失败') ? '下载终止,请重新下载' : _errorMessage,
|
||
style: const TextStyle(color: Colors.red),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
if (_showRetry) ...[
|
||
const SizedBox(height: 16),
|
||
GestureDetector(
|
||
onTap: _startDownload,
|
||
child: Container(
|
||
width: 160,
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xFF80DAA4), Color(0xFF79DDED)],
|
||
begin: Alignment.centerLeft,
|
||
end: Alignment.centerRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(8), // 减小圆角
|
||
),
|
||
child: const Center(
|
||
child: Text(
|
||
'重试',
|
||
style: TextStyle(
|
||
color: Colors.black,
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 10),
|
||
GestureDetector(
|
||
onTap: () {
|
||
Navigator.of(context).pop();
|
||
},
|
||
child: const Text(
|
||
'取消',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
] else ...[
|
||
const Text(
|
||
'下载更新',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
const Icon(
|
||
Icons.check_circle_outline,
|
||
color: Colors.white,
|
||
size: 48,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'下载完成,正在安装...',
|
||
style: TextStyle(
|
||
color: Colors.white.withOpacity(0.5),
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|