Files
observer/flutter_app/lib/update/update_checker.dart
T
admin a0b115d954 训练体系整合与标注单阶段化
- 标注:AI 预标注直写 labels_json(去候选确认两阶段);重叠去重(minIoU);全量标注按钮
- 训练:脚本迁移入 server/training/(Go 化 prepare_yolo/analyze_rfdetr,保留 train_server.py);tflite 产物自检并入训练流程(check_tflite)
- 数据目录/权重不进 git;.gitignore 迁移至仓库根
2026-08-26 18:22:56 +08:00

87 lines
3.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
// 服务器 Content-Type 无 charsethttp 包默认按 latin1 解码会乱码 → 显式 utf8
final body =
jsonDecode(utf8.decode(res.bodyBytes)) 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;
}
}