Optimize automatic updates

This commit is contained in:
2026-05-31 09:32:32 +08:00
parent 8dfc22691a
commit 61ad85f6fc
4 changed files with 175 additions and 98 deletions
+63 -7
View File
@@ -9,10 +9,12 @@ import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:xml/xml.dart';
import '../core/constants/storage_keys.dart';
import '../core/utils/app_logger.dart';
import '../data/models/app_update_model.dart';
import 'storage_service.dart';
class UpdateService {
class UpdateService extends ChangeNotifier {
UpdateService._();
static final UpdateService instance = UpdateService._();
@@ -23,6 +25,9 @@ class UpdateService {
static final Uri releasesPageUrl = Uri.parse(
'https://git.saont.net/gongyun/app/releases',
);
static final Uri downloadsPageUrl = Uri.parse(
'https://www.gongyun.org/downloads.html',
);
final Dio _dio = Dio(
BaseOptions(
@@ -34,17 +39,24 @@ class UpdateService {
);
bool _checking = false;
AppUpdateInfo? _availableUpdate;
AppUpdateInfo? get availableUpdate => _availableUpdate;
bool get hasUpdate => _availableUpdate != null;
Future<AppUpdateInfo?> checkForUpdate() async {
if (_checking) return null;
if (!_supportsAutoDownload) return null;
_checking = true;
try {
final current = await PackageInfo.fromPlatform();
final latest = await _fetchLatestRelease(currentVersion: current.version);
if (latest == null) return null;
if (_compareVersions(latest.version, current.version) <= 0) return null;
if (latest == null ||
_compareVersions(latest.version, current.version) <= 0) {
_setAvailableUpdate(null);
return null;
}
_setAvailableUpdate(latest);
return latest;
} catch (e, stackTrace) {
AppLogger.e('检查更新失败: $e\n$stackTrace');
@@ -54,6 +66,34 @@ class UpdateService {
}
}
Future<bool> shouldShowPrompt(AppUpdateInfo update) async {
final storage = StorageService.instance;
final skipVersion = await storage.getString(
StorageKeys.updatePromptSkipVersion,
);
final skipUntil = await storage.getInt(StorageKeys.updatePromptSkipUntil);
if (skipVersion != update.version || skipUntil == null) return true;
final now = DateTime.now().millisecondsSinceEpoch;
if (skipUntil > now) return false;
await storage.remove(StorageKeys.updatePromptSkipUntil);
await storage.remove(StorageKeys.updatePromptSkipVersion);
return true;
}
Future<void> skipPromptForFiveDays(AppUpdateInfo update) async {
final skipUntil = DateTime.now()
.add(const Duration(days: 5))
.millisecondsSinceEpoch;
final storage = StorageService.instance;
await storage.setString(
StorageKeys.updatePromptSkipVersion,
update.version,
);
await storage.setInt(StorageKeys.updatePromptSkipUntil, skipUntil);
}
Future<File?> downloadUpdate(
AppUpdateInfo update, {
void Function(int received, int total)? onProgress,
@@ -96,6 +136,15 @@ class UpdateService {
}
}
Future<void> openDownloadsPage() async {
if (!await launchUrl(
downloadsPageUrl,
mode: LaunchMode.externalApplication,
)) {
throw Exception('无法打开下载页面: $downloadsPageUrl');
}
}
Future<AppUpdateInfo?> _fetchLatestRelease({String? currentVersion}) async {
final response = await _dio.getUri<String>(releasesRssUrl);
final body = response.data;
@@ -119,7 +168,6 @@ class UpdateService {
final update = await _buildUpdateInfo(release);
if (update == null) continue;
if (update.downloadUrl == null) continue;
return update;
}
@@ -206,8 +254,6 @@ class UpdateService {
return null;
}
bool get _supportsAutoDownload => Platform.isAndroid || Platform.isWindows;
bool _isReleaseAttachment(Uri uri) {
return uri.path.contains('/releases/download/');
}
@@ -345,6 +391,16 @@ class UpdateService {
return Uri.decodeComponent(segment);
}
void _setAvailableUpdate(AppUpdateInfo? update) {
final previous = _availableUpdate;
if (previous?.version == update?.version &&
previous?.pageUrl == update?.pageUrl) {
return;
}
_availableUpdate = update;
notifyListeners();
}
@visibleForTesting
Future<AppUpdateInfo?> debugFetchLatestRelease() => _fetchLatestRelease();
}