feat(client): 应用内更新(Windows/macOS 下载安装,Web 刷新)

- 新增 lib/core/update/:app_updater(入口+进度对话框) + io/web 条件实现
  - Windows: Dio 下载 setup.exe(无 MOTW) -> 启动安装包 -> 退出,规避浏览器
    SmartScreen 下载页与运行时拦截
  - macOS: 下载 zip -> ditto 解压 -> 后台脚本等退出后替换 /Applications 内
    app 并重启(权限失败回退访达高亮)
  - Web: 刷新页面;其它平台回退浏览器下载
- app_shell 更新 banner/强制更新弹窗、设置页「立即更新」改走应用内更新
- 设置页「检查更新」加反馈(正在检查/已是最新/发现新版本/失败)
- jiu-installer.iss 加 CloseApplications=yes 便于覆盖

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-05 09:04:26 +08:00
parent 056ae914ce
commit d8c0bd74d2
6 changed files with 210 additions and 6 deletions
@@ -12,6 +12,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../core/auth/auth_state.dart';
import '../../core/config/app_config.dart';
import '../../core/config/app_info.dart';
import '../../core/update/app_updater.dart';
import '../../core/theme/app_theme.dart';
import '../../models/number_rule.dart';
import '../../models/user.dart';
@@ -729,7 +730,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
value: 'v${updateInfo.latestVersion}',
valueColor: AppTheme.success,
trailing: TextButton(
onPressed: () => launchUpdateUrl(updateInfo.downloadUrls),
onPressed: () => startInAppUpdate(context, updateInfo),
child: const Text('立即更新'),
),
)
@@ -739,8 +740,24 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
value: updateInfo != null ? '已是最新' : '检查中…',
valueColor: AppTheme.textSecondary,
trailing: TextButton(
onPressed: () =>
ref.read(updateProvider.notifier).forceCheck(),
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
const SnackBar(content: Text('正在检查更新…')));
await ref.read(updateProvider.notifier).forceCheck();
if (!context.mounted) return;
final r = ref.read(updateProvider).valueOrNull;
messenger.hideCurrentSnackBar();
final String msg;
if (r == null) {
msg = '检查更新失败,请检查网络';
} else if (r.hasUpdate) {
msg = '发现新版本 v${r.latestVersion}';
} else {
msg = '已是最新版本(v${r.latestVersion}';
}
messenger.showSnackBar(SnackBar(content: Text(msg)));
},
child: const Text('检查更新'),
),
),
+4 -3
View File
@@ -10,6 +10,7 @@ import '../../core/theme/app_theme.dart';
import '../../providers/connectivity_provider.dart';
import '../../providers/shop_provider.dart';
import '../../providers/update_provider.dart';
import '../../core/update/app_updater.dart';
class AppShell extends ConsumerStatefulWidget {
final Widget child;
@@ -56,7 +57,7 @@ class _AppShellState extends ConsumerState<AppShell> {
),
actions: [
ElevatedButton(
onPressed: () => launchUpdateUrl(info.downloadUrls),
onPressed: () => startInAppUpdate(context, info),
child: const Text('立即更新'),
),
],
@@ -237,8 +238,8 @@ class _AppShellState extends ConsumerState<AppShell> {
),
),
TextButton(
onPressed: () => launchUpdateUrl(
updateInfo.downloadUrls),
onPressed: () =>
startInAppUpdate(context, updateInfo),
style: TextButton.styleFrom(
foregroundColor:
const Color(0xFFF57F17)),