ar_tourism_flutter_unity/lib/services/update_dialog_service.dart
2025-05-14 17:04:13 +08:00

503 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
),
),
],
],
),
),
);
}
}