feat: add app update checks and standalone release publishing panel

This commit is contained in:
Codex
2026-10-24 13:50:42 +08:00
parent 437ed14dbe
commit 75e0b5587d
27 changed files with 1566 additions and 7 deletions
@@ -24,6 +24,8 @@ class ProfilePage extends StatelessWidget {
this.logoutLabel = '退出学校登录',
this.onUpdateTerm,
this.feedbackSender = const UnconfiguredFeedbackSender(),
this.onCheckUpdates,
this.checkingUpdates = false,
});
final AppController controller;
final VoidCallback onLogin;
@@ -32,6 +34,8 @@ class ProfilePage extends StatelessWidget {
final VoidCallback? onUpdateTerm;
final String authStatus, logoutLabel;
final FeedbackSender feedbackSender;
final VoidCallback? onCheckUpdates;
final bool checkingUpdates;
Future<void> _import(BuildContext context) async {
try {
final file = await FilePicker.pickFile(
@@ -231,6 +235,32 @@ class ProfilePage extends StatelessWidget {
),
),
),
Divider(height: 1, color: ink.withValues(alpha: 0.06)),
ListTile(
key: const Key('check-app-update'),
contentPadding: EdgeInsets.zero,
leading: const Icon(
Icons.system_update_rounded,
color: river,
size: 23,
),
title: const Text(
'检查更新',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
subtitle: Text(
checkingUpdates ? '正在检查…' : '当前版本 $appVersion',
style: const TextStyle(fontSize: 11),
),
trailing: checkingUpdates
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.chevron_right_rounded),
onTap: checkingUpdates ? null : onCheckUpdates,
),
],
),
),
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import '../../app/app_version.dart';
import '../../services/app_update_service.dart';
Future<void> showUpdatePrompt(
BuildContext context,
AppRelease release, {
UpdateDownloadOpener openDownload = openUpdateInBrowser,
}) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('发现新版本 ${release.version}'),
content: SizedBox(
width: 360,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('当前版本 $appVersion', style: TextStyle(fontSize: 12)),
const SizedBox(height: 16),
Text(release.notes, style: const TextStyle(height: 1.6)),
const SizedBox(height: 16),
const Text(
'将打开浏览器下载,安装时请保留现有应用和课表。',
style: TextStyle(fontSize: 12),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('稍后'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('立即更新'),
),
],
),
);
if (confirmed != true || !context.mounted) return;
bool opened;
try {
opened = await openDownload(release.downloadUri);
} catch (_) {
opened = false;
}
if (!opened && context.mounted) {
await showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('未能打开浏览器'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('可以复制下方链接到浏览器下载:'),
const SizedBox(height: 12),
SelectableText(release.downloadUri.toString()),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('知道了'),
),
],
),
);
}
}