Files
pangolin/client/lib/state/update_provider.dart
T
wangjia 6277e86d0f
Deploy Server / deploy-server (push) Successful in 2m56s
feat(client): SSO 用户中心按钮 + 自动更新检查(镜像 jiu)
SSO: web_launch.openWebUserCenter → POST /v1/auth/web-ticket → 打开 app.yanmeiai.com/sso?t=…;
设置页加「用户中心(网页)」入口。自动更新: update_provider 查 /version 语义比对 + update_dialog
「发现新版本」;设置页「检查更新」由空 onTap 接上。加 url_launcher(原缺)。flutter analyze 通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 00:13:50 +08:00

106 lines
3.7 KiB
Dart

// update_provider.dart — 应用更新检查(轻量版,无后台轮询)。
//
// 手动触发(设置页「检查更新」)向控制面 GET $kApiBaseUrl/version 拉取最新版本信息,
// 与本地版本(package_info_plus)做语义比较;有更新则由调用方(settings_page)弹窗
// 展示(见 widgets/update_dialog.dart)。检查失败静默返回 null,不影响主流程。
//
// 对照 jiu client/lib/providers/update_provider.dart,去掉了定时轮询 Timer 与
// dismiss 状态——按需求这里只做「手动检查」,启动期自动检查留作后续 TODO。
import 'dart:convert';
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import '../services/api_config.dart';
/// 一次更新检查的结果。
class AppUpdateInfo {
const AppUpdateInfo({
required this.latestVersion,
required this.buildNumber,
required this.forceUpdate,
required this.releaseNotes,
required this.downloadUrls,
required this.hasUpdate,
});
final String latestVersion;
final int buildNumber;
final bool forceUpdate;
final String releaseNotes;
final Map<String, String> downloadUrls;
final bool hasUpdate;
}
/// 无状态更新检查服务,供设置页「检查更新」按钮直接调用。
final updateCheckerProvider = Provider<UpdateChecker>((ref) => const UpdateChecker());
class UpdateChecker {
const UpdateChecker();
/// 拉取 `$kApiBaseUrl/version` 并与本地版本比较。网络/解析失败返回 null(静默)。
Future<AppUpdateInfo?> check() async {
try {
final resp = await http
.get(Uri.parse('$kApiBaseUrl/version'))
.timeout(const Duration(seconds: 8));
if (resp.statusCode != 200) return null;
final data = jsonDecode(resp.body) as Map<String, dynamic>;
final latestVersion = data['version'] as String? ?? '0.0.0';
final buildNumber = (data['build_number'] as num?)?.toInt() ?? 0;
final forceUpdate = data['force_update'] as bool? ?? false;
final releaseNotes = data['release_notes'] as String? ?? '';
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);
return AppUpdateInfo(
latestVersion: latestVersion,
buildNumber: buildNumber,
forceUpdate: forceUpdate,
releaseNotes: releaseNotes,
downloadUrls: downloadUrls,
hasUpdate: hasUpdate,
);
} catch (_) {
return null;
}
}
/// 语义化版本比较:latest > current → true。容忍 1.1.4-dev / 1.1.4+7 等后缀。
bool _isNewer(String latest, String current) {
final l = _parse(latest);
final c = _parse(current);
for (var i = 0; i < 3; i++) {
if (l[i] > c[i]) return true;
if (l[i] < c[i]) return false;
}
return false;
}
List<int> _parse(String v) {
final parts = v.split('.').map((s) {
final m = RegExp(r'^\d+').firstMatch(s);
return m == null ? 0 : int.parse(m.group(0)!);
}).toList();
while (parts.length < 3) {
parts.add(0);
}
return parts;
}
}
/// 按当前平台从服务端 download_urls 里取对应下载直链。
String? platformDownloadUrl(Map<String, String> downloadUrls) {
if (Platform.isMacOS) return downloadUrls['macos'];
if (Platform.isWindows) return downloadUrls['windows'];
if (Platform.isIOS) return downloadUrls['ios'];
if (Platform.isAndroid) return downloadUrls['android'];
return downloadUrls['web'];
}