85 lines
2.9 KiB
Dart
85 lines
2.9 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../config/app_config.dart';
|
|
|
|
/// App 版本更新信息(GET /api/v1/app/update 响应 data;无记录时为空信息)
|
|
class AppUpdateInfo {
|
|
final String version;
|
|
final String notes;
|
|
|
|
const AppUpdateInfo({required this.version, required this.notes});
|
|
|
|
/// 服务器无版本记录时返回空信息,调用方视为无需更新
|
|
bool get isEmpty => version.isEmpty;
|
|
}
|
|
|
|
/// 启动时版本更新检查:仅 Android 检查;服务器版本高于本地版本即强制更新
|
|
/// (无普通/强制之分)。
|
|
class UpdateChecker {
|
|
final String baseUrl;
|
|
final http.Client _client;
|
|
|
|
UpdateChecker({String? baseUrl, http.Client? client})
|
|
: baseUrl = baseUrl ?? AppConfig.apiBaseUrl,
|
|
_client = client ?? http.Client();
|
|
|
|
/// 下载地址:固定静态路径(管理端上传的 APK 覆盖保存为固定文件)
|
|
static String downloadUrl({String? baseUrl}) =>
|
|
'${baseUrl ?? AppConfig.apiBaseUrl}/download/observer-latest.apk';
|
|
|
|
/// 拉取服务器最新版本;非 Android 或网络异常时返回空信息,不阻塞启动
|
|
Future<AppUpdateInfo> fetch() async {
|
|
if (!Platform.isAndroid) {
|
|
return const AppUpdateInfo(version: '', notes: '');
|
|
}
|
|
try {
|
|
final res = await _client
|
|
.get(Uri.parse('$baseUrl/api/v1/app/update'))
|
|
.timeout(const Duration(seconds: 8));
|
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
|
final data = body['data'] as Map<String, dynamic>? ?? const {};
|
|
return AppUpdateInfo(
|
|
version: data['version'] as String? ?? '',
|
|
notes: data['notes'] as String? ?? '',
|
|
);
|
|
} catch (_) {
|
|
return const AppUpdateInfo(version: '', notes: '');
|
|
}
|
|
}
|
|
|
|
/// 是否需要更新:服务器版本高于「已装版本与已确认接受版本」中的较大者。
|
|
/// APK 版本号不递增时,用户点过「立即更新」后 accepted 追上服务器版本,
|
|
/// 已更新完成再次启动也不会反复提示。
|
|
static bool needsUpdate(String server, String installed, String accepted) {
|
|
if (server.isEmpty || installed.isEmpty) return false;
|
|
return isNewer(server, installed) || isNewer(server, accepted);
|
|
}
|
|
|
|
/// 语义化版本号比较:a > b 返回 true。按数字段比较(1.10.0 > 1.9.9),
|
|
/// 任一版本号解析失败时视为相等(不触发更新)。
|
|
static bool isNewer(String a, String b) {
|
|
final pa = _parse(a);
|
|
final pb = _parse(b);
|
|
if (pa == null || pb == null) return false;
|
|
for (var i = 0; i < 3; i++) {
|
|
if (pa[i] != pb[i]) return pa[i] > pb[i];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static List<int>? _parse(String v) {
|
|
final parts = v.split('.');
|
|
if (parts.length != 3) return null;
|
|
final nums = <int>[];
|
|
for (final p in parts) {
|
|
final n = int.tryParse(p);
|
|
if (n == null) return null;
|
|
nums.add(n);
|
|
}
|
|
return nums;
|
|
}
|
|
}
|