feat(client/update): 启动自动检查 + App 内下载安装(不走浏览器)
Deploy Client / build-windows (push) Successful in 2m2s
Deploy Client / build-android (push) Successful in 2m13s
Deploy Client / build-macos (push) Successful in 3m45s
Deploy Client / build-ios (push) Successful in 3m58s
Deploy Client / release-deploy (push) Successful in 2m1s
Deploy Client / build-windows (push) Successful in 2m2s
Deploy Client / build-android (push) Successful in 2m13s
Deploy Client / build-macos (push) Successful in 3m45s
Deploy Client / build-ios (push) Successful in 3m58s
Deploy Client / release-deploy (push) Successful in 2m1s
参照 jiu 补齐,但比 jiu 多做了 Android 原生安装(jiu 的 App 内下载仅 Win/mac): - 启动自动检查:update_provider 改 AsyncNotifier,build() 延迟 3s 首查 + 每小时 轮询;home_shell watch 拉起、listen 弹更新框;_dismissed 防反复弹(轮询重置); 强制更新走不可关闭弹窗。设置页「检查更新」改 forceCheck()。 - App 内下载(core/update/app_updater.dart,http 流式带进度框,不再 launchUrl): · Android:下 APK → open_filex 拉起系统安装器(+ REQUEST_INSTALL_PACKAGES) · Windows:下 exe → Process.start(detached) → exit(0) 覆盖安装 · macOS :下 zip → open 解压 + 访达显示,提示手动拖入(带系统扩展不自动 swap) · iOS :降级外链(TestFlight/App Store) 失败降级浏览器下载。新增 6 语更新进度/失败/提示文案。 - 依赖 open_filex;flutter analyze 仅 2 个既有 withOpacity info。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
// update_provider.dart — 应用更新检查(轻量版,无后台轮询)。
|
||||
// update_provider.dart — 应用更新检查(启动自动检查 + 定时轮询)。
|
||||
//
|
||||
// 手动触发(设置页「检查更新」)向控制面 GET $kApiBaseUrl/version 拉取最新版本信息,
|
||||
// 与本地版本(package_info_plus)做语义比较;有更新则由调用方(settings_page)弹窗
|
||||
// 展示(见 widgets/update_dialog.dart)。检查失败静默返回 null,不影响主流程。
|
||||
// 对照 jiu client/lib/providers/update_provider.dart:用 AsyncNotifier 的惰性
|
||||
// build() 做「启动后延迟首查 + 每小时轮询」;shell 里 watch 一次即拉起整条流程。
|
||||
// `_dismissed` 是本次进程内存标志(用户点「稍后」置真,防反复弹),每轮轮询重置。
|
||||
// 强制更新(force_update)不受 dismiss 影响,由 UI 走不可关闭弹窗。
|
||||
//
|
||||
// 对照 jiu client/lib/providers/update_provider.dart,去掉了定时轮询 Timer 与
|
||||
// dismiss 状态——按需求这里只做「手动检查」,启动期自动检查留作后续 TODO。
|
||||
// 拿到更新后,下载安装走 core/update/app_updater.dart(App 内下载,不再开浏览器)。
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -15,6 +16,15 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import '../services/api_config.dart';
|
||||
|
||||
/// 启动后首次检查的延迟(避开登录/首屏竞争)。
|
||||
const _kInitialDelay = Duration(seconds: 3);
|
||||
|
||||
/// 轮询间隔。
|
||||
const _kPollInterval = Duration(hours: 1);
|
||||
|
||||
/// 单次检查网络超时。
|
||||
const _kCheckTimeout = Duration(seconds: 8);
|
||||
|
||||
/// 一次更新检查的结果。
|
||||
class AppUpdateInfo {
|
||||
const AppUpdateInfo({
|
||||
@@ -34,18 +44,43 @@ class AppUpdateInfo {
|
||||
final bool hasUpdate;
|
||||
}
|
||||
|
||||
/// 无状态更新检查服务,供设置页「检查更新」按钮直接调用。
|
||||
final updateCheckerProvider = Provider<UpdateChecker>((ref) => const UpdateChecker());
|
||||
/// 更新检查 Notifier。build() 惰性触发:延迟首查 + 每小时轮询。
|
||||
class UpdateNotifier extends AsyncNotifier<AppUpdateInfo?> {
|
||||
Timer? _timer;
|
||||
bool _dismissed = false;
|
||||
|
||||
class UpdateChecker {
|
||||
const UpdateChecker();
|
||||
/// 本次会话是否已被用户「稍后」忽略(强制更新不看这个)。
|
||||
bool get isDismissed => _dismissed;
|
||||
|
||||
@override
|
||||
Future<AppUpdateInfo?> build() async {
|
||||
ref.onDispose(() => _timer?.cancel());
|
||||
await Future<void>.delayed(_kInitialDelay);
|
||||
final first = await _check();
|
||||
_timer = Timer.periodic(_kPollInterval, (_) async {
|
||||
_dismissed = false; // 新一轮允许再次提示
|
||||
state = AsyncValue.data(await _check());
|
||||
});
|
||||
return first;
|
||||
}
|
||||
|
||||
/// 用户点「稍后」——本次会话内不再自动弹(直到下轮轮询)。
|
||||
void dismiss() => _dismissed = true;
|
||||
|
||||
/// 设置页「检查更新」手动触发:立即查一次并回结果(不受 dismiss 影响)。
|
||||
Future<AppUpdateInfo?> forceCheck() async {
|
||||
_dismissed = false;
|
||||
final info = await _check();
|
||||
state = AsyncValue.data(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
/// 拉取 `$kApiBaseUrl/version` 并与本地版本比较。网络/解析失败返回 null(静默)。
|
||||
Future<AppUpdateInfo?> check() async {
|
||||
Future<AppUpdateInfo?> _check() async {
|
||||
try {
|
||||
final resp = await http
|
||||
.get(Uri.parse('$kApiBaseUrl/version'))
|
||||
.timeout(const Duration(seconds: 8));
|
||||
.timeout(_kCheckTimeout);
|
||||
if (resp.statusCode != 200) return null;
|
||||
final data = jsonDecode(resp.body) as Map<String, dynamic>;
|
||||
|
||||
@@ -56,8 +91,8 @@ class UpdateChecker {
|
||||
final rawUrls = data['download_urls'] as Map<String, dynamic>? ?? const {};
|
||||
final downloadUrls = rawUrls.map((k, v) => MapEntry(k, v?.toString() ?? ''));
|
||||
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final hasUpdate = _isNewer(latestVersion, info.version);
|
||||
final pkg = await PackageInfo.fromPlatform();
|
||||
final hasUpdate = _isNewer(latestVersion, pkg.version);
|
||||
|
||||
return AppUpdateInfo(
|
||||
latestVersion: latestVersion,
|
||||
@@ -95,6 +130,9 @@ class UpdateChecker {
|
||||
}
|
||||
}
|
||||
|
||||
final updateProvider =
|
||||
AsyncNotifierProvider<UpdateNotifier, AppUpdateInfo?>(UpdateNotifier.new);
|
||||
|
||||
/// 按当前平台从服务端 download_urls 里取对应下载直链。
|
||||
String? platformDownloadUrl(Map<String, String> downloadUrls) {
|
||||
if (Platform.isMacOS) return downloadUrls['macos'];
|
||||
|
||||
Reference in New Issue
Block a user