import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../models/app_version.dart'; import '../utils/session_org_scope.dart'; import 'api_client.dart'; /// 版本更新检测结果。 /// /// [hasUpdate] 是否检测到更高版本;[latest] 服务端最新可用版本(无更新时为 null)。 class UpdateCheckResult { const UpdateCheckResult({required this.hasUpdate, this.latest}); final bool hasUpdate; final AppVersion? latest; } /// 版本更新服务:检测当前 App 是否有新版本可更新。 /// /// 对齐 Web 端 `searchAppVersion`:按当前登录用户所属组织查询该组织配置的 /// APP 版本(`GET /device/orgGroupAppVersion/search?orgId=...`),再与本地 /// versionCode 比较。 /// /// 注意:这里不再调用 `/device/orgGroupAppVersion/list`。`/list` 是管理后台 /// 全量枚举接口,普通用户常因权限不足收到「未知异常,请联系管理员」; /// `/search` 才是「查询某组织当前应使用版本」的语义入口。 class UpdateService { UpdateService({required this.packageName, ApiClient? client}) : _client = client ?? ApiClient.instance; /// 当前 App 包名(运行时由 package_info_plus 取得,如 com.tongshi.cloudhome)。 final String packageName; final ApiClient _client; /// 查询当前用户所属组织配置的版本,并判定是否有更新。 /// /// - 优先使用传入的 [orgId],未传入时回退到 [SessionOrgScope.instance.currentOrgId]; /// - 若最终没有可用 orgId(管理员/未识别),直接返回「无更新」,避免向后端 /// 发起缺少必要参数的请求; /// - 调用 `/device/orgGroupAppVersion/search?orgId=xxx` 获取该组织配置的版本; /// - 兼容返回体为 `null` / `{appVersion: {...}}` / 直接版本对象三种形态; /// - 仅当返回版本 `status==0` 且 versionCode 大于当前版本时才认为有更新; /// - 若后端返回了 packageName 且与当前 App 包名不一致,也认为无更新,防止 /// 不同包被错误提示。 /// /// 任一异常均向上抛出,由调用方决定是否静默忽略(启动时检测失败不应阻断使用)。 Future checkForUpdate( int currentVersionCode, { String? orgId, }) async { final effectiveOrgId = (orgId != null && orgId.isNotEmpty) ? orgId : (SessionOrgScope.instance.enabled ? SessionOrgScope.instance.currentOrgId : null); if (effectiveOrgId == null || effectiveOrgId.isEmpty) { if (kDebugMode) { debugPrint('[UpdateService] 缺少 orgId,跳过更新检查'); } return const UpdateCheckResult(hasUpdate: false); } if (kDebugMode) { debugPrint( '[UpdateService] orgId=$effectiveOrgId currentCode=$currentVersionCode', ); } final response = await _client.dio.get( '/device/orgGroupAppVersion/search', queryParameters: {'orgId': effectiveOrgId}, ); final latest = _parseVersion(response.data); if (latest == null) { if (kDebugMode) debugPrint('[UpdateService] 未查询到组织版本配置'); return const UpdateCheckResult(hasUpdate: false); } if (kDebugMode) { debugPrint( '[UpdateService] server versionCode=${latest.versionCode} ' 'versionName=${latest.versionName} status=${latest.status} ' 'packageName=${latest.packageName}', ); } if (latest.status != 0) { return const UpdateCheckResult(hasUpdate: false); } // 后端配置了 packageName 但与当前 App 不匹配时,不提示更新 //(常见于同一组织下存在多个不同包的场景)。 if (latest.packageName.isNotEmpty && latest.packageName != packageName) { return const UpdateCheckResult(hasUpdate: false); } return UpdateCheckResult( hasUpdate: latest.versionCode > currentVersionCode, latest: latest, ); } /// 解析 `/search` 返回的三种形态: /// - `null`:该组织没有配置版本; /// - `{appVersion: {...}}`:Web 端标准形态; /// - 直接返回版本对象。 AppVersion? _parseVersion(dynamic data) { if (data == null) return null; if (data is! Map) return null; final appVersion = data['appVersion']; if (appVersion is Map) { return AppVersion.fromJson(appVersion); } if (data.containsKey('id') || data.containsKey('versionName')) { return AppVersion.fromJson(data); } return null; } } /// 读取当前 App 的 [PackageInfo](包名 / versionCode / versionName)。 /// /// 集中封装以便多处复用,并在 [PackageInfo] 未初始化时提供兜底。 Future getCurrentPackageInfo() => PackageInfo.fromPlatform();