import 'dart:io'; import 'dart:math' as math; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:logger/logger.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '../models/channel_app_model.dart'; import 'network/api.dart'; /// 版本更新服务类 class VersionUpdateService extends GetxService { static VersionUpdateService get to => Get.find(); // 静态应用信息(内存缓存) static String? _appVersion = ""; //应用版本号 static String? _buildNumber = ""; //构建号 static String? _platform = ""; //平台类型 static String? _deviceBrand = ""; //设备品牌 static UserChannel? _channel; //渠道 static String? _latestVersion = ""; //最新版本号+构建号 static bool? _hasUpdate = false; //是否有更新 static bool? _isForceUpdate = false; //是否强制更新 static String? _updateUrl = ""; //更新链接 static AppInfo? _appInfo; //应用信息 static bool? _isShowTourists = false; //是否显示游客登录 static bool? _allowSkipDeviceBinding = false; //是否允许跳过设备绑定 static DateTime? _lastCheckTime; //上次检查时间 static const int _cacheIntervalMinutes = 10; // 可配置化 static final String? _countryCode = Get.deviceLocale?.countryCode; //国家地区编码 static final String? _languageCode = Get.deviceLocale?.languageCode; //语言编码 // ignore: non_constant_identifier_names // static final String? _SERVER_URL = (_countryCode == 'CN') // ? dotenv.env['SERVER_URL'] // : dotenv.env['HW_SERVER_URL']; //服务器地址 // 添加 getter 方法来访问缓存的静态数据 /// 获取缓存的应用版本 String get cachedAppVersion => _appVersion ?? ''; /// 获取缓存的构建号 String get cachedBuildNumber => _buildNumber ?? ''; /// 获取缓存的平台信息 String get cachedPlatform => _platform ?? ''; /// 获取缓存的设备品牌 String get cachedDeviceBrand => _deviceBrand ?? ''; /// 获取缓存的渠道信息 UserChannel get cachedChannel => _channel ?? (_platform?.toLowerCase() == 'ios' ? UserChannel.GLOBAL_APPLE : UserChannel.GLOBAL_GOOGLE); /// 获取缓存的最新版本 String get cachedLatestVersion => _latestVersion ?? ''; /// 获取缓存的是否有更新 bool get cachedHasUpdate => _hasUpdate ?? false; /// 获取缓存的是否强制更新 bool get cachedIsForceUpdate => _isForceUpdate ?? false; /// 获取缓存的更新地址 String get cachedUpdateUrl => _updateUrl ?? ''; /// 获取缓存的应用信息 AppInfo? get cachedAppInfo => _appInfo; /// 获取缓存的游客登录状态 bool get cachedShowTourists => _isShowTourists ?? false; /// 获取缓存的是否允许跳过设备绑定 bool get cachedAllowSkipDeviceBinding => _allowSkipDeviceBinding ?? false; /// 获取上次检查时间 DateTime? get lastCheckTime => _lastCheckTime; /// 获取缓存的国家地区编码 String get cachedCountryCode => _countryCode ?? ''; /// 获取缓存的语言编码 String get cachedLanguageCode => _languageCode ?? ''; /// 获取缓存的服务器地址 // String get cachedServerUrl => _SERVER_URL ?? ''; final Logger _logger = Logger(); // @override // void onInit() { // getCurrentVersion(); // getCurrentPlatformInfo(); // super.onInit(); // } /// 检查版本更新 Future checkForUpdate() async { if (_lastCheckTime != null && DateTime.now().difference(_lastCheckTime!).inMinutes < _cacheIntervalMinutes) { // [1]没有超过10分钟,返回缓存数据 _logger.d('没有超过10分钟,返回缓存数据'); return VersionUpdateInfo( hasUpdate: _hasUpdate ?? false, currentVersion: _appVersion ?? '1.0.0', latestVersion: (_latestVersion ?? '1.0.0').split('+')[0], appInfo: _appInfo, updateType: _hasUpdate! ? (_isForceUpdate! ? UpdateType.force : UpdateType.optional) : UpdateType.none, ); } try { final currentVersion = await getCurrentVersion(); final platformInfo = await getCurrentPlatformInfo(); _platform = platformInfo.platform; //_channel = platformInfo.channel; _lastCheckTime = DateTime.now(); // 获取所有渠道包信息 final response = await Api.getChannelApps(); _logger.d('lxm--获取所有渠道包信息:$response'); final List apps = _parseAppsList(response); if (apps.isEmpty) { _logger.w('lxm--获取所有渠道包信息失败或为空'); return VersionUpdateInfo( hasUpdate: false, currentVersion: currentVersion, latestVersion: currentVersion, updateType: UpdateType.none, ); } final Map appIndex = _buildChannelIndex(apps); // 依据设备品牌渠道用于版本更新链接 final brandChannel = platformInfo.channel; // 环境变量渠道用于游客登录配置 // final envChannelName = dotenv.env['Channel'] ?? 'OFFICIAL'; final envChannel = _channel; final AppInfo? envAppInfo = appIndex[envChannel]; // 若不存在品牌渠道信息,降级为环境渠道;仍不存在则返回无更新 final AppInfo? brandAppInfo = appIndex[brandChannel] ?? envAppInfo; if (brandAppInfo == null) { _logger.w('lxm--未找到品牌或环境渠道的应用信息,返回无更新'); return VersionUpdateInfo( hasUpdate: false, currentVersion: currentVersion, latestVersion: currentVersion, updateType: UpdateType.none, ); } _appInfo = brandAppInfo; _latestVersion = brandAppInfo.version; _updateUrl = brandAppInfo.address; // 设置游客登录显示状态(先用当前渠道结果作为回退,再用.env渠道覆盖以保持一致) _isShowTourists = (envAppInfo ?? brandAppInfo).tourists; _allowSkipDeviceBinding = (envAppInfo ?? brandAppInfo).allowskipdevicebinding; _logger.d('lxm--从服务器获取游客登录配置(最终): ${_isShowTourists}'); // if (channelAppResponse.code != 0 || channelAppResponse.data == null) { // _logger.w('获取版本信息失败: ${channelAppResponse.msg}'); // return VersionUpdateInfo( // hasUpdate: false, // currentVersion: currentVersion, // latestVersion: currentVersion, // updateType: UpdateType.none, // ); // } // final appInfo = channelAppResponse.data!.app; _hasUpdate = _compareVersions(currentVersion, brandAppInfo.version); // 判断更新类型:如果主版本号不同则强制更新 UpdateType updateType = UpdateType.none; if (_hasUpdate!) { _isForceUpdate = _shouldForceUpdate(currentVersion, brandAppInfo.version); updateType = _isForceUpdate! ? UpdateType.force : UpdateType.optional; } //[3]返回版本更新信息 return VersionUpdateInfo( hasUpdate: _hasUpdate!, currentVersion: currentVersion.split('+')[0], latestVersion: brandAppInfo.version.split('+')[0], appInfo: brandAppInfo, updateType: updateType, ); } catch (e) { _logger.e('检查版本更新失败: $e'); // 发生错误时,保持默认值 false _isShowTourists = false; final currentVersion = await getCurrentVersion(); return VersionUpdateInfo( hasUpdate: false, currentVersion: currentVersion, latestVersion: currentVersion, updateType: UpdateType.none, ); } } /// 获取当前应用版本信息 Future getCurrentVersion() async { try { final packageInfo = await PackageInfo.fromPlatform(); _appVersion = packageInfo.version; _buildNumber = packageInfo.buildNumber; return '${packageInfo.version}+${packageInfo.buildNumber}'; } catch (e) { _logger.e('获取当前版本信息失败: $e'); return '1.0.0'; } } /// 获取当前平台信息 Future getCurrentPlatformInfo() async { if (Platform.isIOS) { _channel = UserChannel.GLOBAL_APPLE; await _getIosDeviceBrand(); return const PlatformInfo( platform: 'ios', storeType: AppStoreType.appStore, channel: UserChannel.GLOBAL_APPLE, ); } if (Platform.isAndroid) { // 根据设备品牌判断应用商店类型 return await getAndroidStoreInfoByBrand(); } return const PlatformInfo( platform: 'unknown', storeType: AppStoreType.unknown, channel: UserChannel.UNKNOWN, ); } /// 根据设备品牌获取应用商店信息 /// /// 该方法根据设备品牌判断应用商店类型,并返回对应的 [PlatformInfo] 对象。 /// 如果品牌不在预设列表中,将使用环境变量 [Channel] 或默认值 [UserChannel.GLOBAL_GOOGLE]。 Future getAndroidStoreInfoByBrand() async { try { final deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; final brand = androidInfo.brand.toLowerCase(); _deviceBrand = brand; String channelFromEnv = dotenv.env['Channel'] ?? 'OFFICIAL'; Logger().d('channelFromEnv: $channelFromEnv'); _channel = UserChannel.fromName(channelFromEnv); UserChannel brandChannel; //品牌渠道,只用于请求版本更新信息 switch (brand) { case 'huawei': brandChannel = UserChannel.CHINA_HUAWEI; break; case 'xiaomi': case 'redmi': brandChannel = UserChannel.CHINA_XIAOMI; break; case 'oppo': case 'oneplus': brandChannel = UserChannel.CHINA_OPPO; break; case 'vivo': case 'iqoo': brandChannel = UserChannel.CHINA_VIVO; break; case 'honor': brandChannel = UserChannel.CHINA_HONOR; break; default: brandChannel = _channel ?? UserChannel.GLOBAL_GOOGLE; break; } return PlatformInfo( platform: 'android', storeType: AppStoreType.official, channel: brandChannel, ); } catch (e) { _logger.e('获取Android设备信息失败: $e'); return const PlatformInfo( platform: 'android', storeType: AppStoreType.googlePlay, channel: UserChannel.UNKNOWN, ); } } /// 获取iOS设备品牌信息 Future _getIosDeviceBrand() async { try { final deviceInfo = DeviceInfoPlugin(); final iosInfo = await deviceInfo.iosInfo; // 获取设备型号信息 final model = iosInfo.model.toLowerCase(); // 例如: "iPhone", "iPad", "iPod touch" //print('lxm---model: $model'); final utsname = iosInfo.utsname; // 包含更详细的设备信息 //print('lxm---utsname: $utsname'); final machineId = utsname.machine.toLowerCase(); // 例如: "iPhone14,2" //print('lxm---machineId: $machineId'); // 根据设备型号确定品牌 if (model.contains('iphone') || machineId.contains('iphone')) { _deviceBrand = 'iphone'; } else if (model.contains('ipad') || machineId.contains('ipad')) { _deviceBrand = 'ipad'; } else if (model.contains('ipod') || machineId.contains('ipod')) { _deviceBrand = 'ipod'; } else if (model.contains('mac') || machineId.contains('mac')) { _deviceBrand = 'mac'; } else { _deviceBrand = 'ios'; // 默认值 } _logger.d('iOS设备品牌: $_deviceBrand (model: $model, machine: $machineId)'); } catch (e) { _logger.e('获取iOS设备品牌失败: $e'); _deviceBrand = 'ios'; // 默认值 } } /// 判断是否需要强制更新 /// 当主版本号(第一位数字)不同时返回true bool _shouldForceUpdate(String current, String latest) { try { // 拆分版本号与构建号 final currentParts = current.split('+'); final latestParts = latest.split('+'); // 解析版本号 (major.minor.patch) final currentVersions = currentParts[0].split('.').map(int.parse).toList(); final latestVersions = latestParts[0].split('.').map(int.parse).toList(); // 比较主版本号(第一位) if (currentVersions.isNotEmpty && latestVersions.isNotEmpty) { if (currentVersions[0] != latestVersions[0]) { return true; } } // 检查是否有两个或更多的 + 号,如果有则强制更新 if (latest.split('+').length > 2) { return true; } return false; } catch (e) { _logger.e('判断强制更新失败: $e'); return false; } } /// 比较版本号 /// 返回true表示需要更新(远程版本更高) bool _compareVersions(String current, String latest) { // 拆分版本号与构建号 final currentParts = current.split('+')[0]; final latestParts = latest.split('+')[0]; // 解析版本号 (major.minor.patch) final currentVersions = currentParts.split('.').map(int.parse).toList(); final latestVersions = latestParts.split('.').map(int.parse).toList(); // 使用较小的长度进行比较,避免索引越界 final minLength = math.min(currentVersions.length, latestVersions.length); // 逐级比较版本号 for (var i = 0; i < minLength; i++) { if (latestVersions[i] > currentVersions[i]) return true; if (latestVersions[i] < currentVersions[i]) return false; } // 如果前面的版本号都相同,比较长度 // 更长的版本号被认为是更新的版本(如 1.0.1 > 1.0) return latestVersions.length > currentVersions.length; } /// 解析响应数据为AppInfo列表 List _parseAppsList(dynamic response) { try { final appsJson = (response is Map) ? response['apps'] : null; if (appsJson is List) { return appsJson .whereType>() .map((e) => AppInfo.fromJson(e)) .toList(); } } catch (e) { _logger.e('解析渠道列表失败: $e'); } return []; } /// 构建渠道索引 Map _buildChannelIndex(List apps) { final index = {}; for (final app in apps) { index[app.channel] = app; } return index; } /// 解析版本号字符串为数字数组 List _parseVersion(String version) { final parts = version.split('.'); final result = []; for (int i = 0; i < 3; i++) { if (i < parts.length) { result.add(int.tryParse(parts[i]) ?? 0); } else { result.add(0); } } return result; } /// 执行更新操作 Future performUpdate(VersionUpdateInfo updateInfo) async { //print('lxm---updateInfo详情:'); //print('lxm---hasUpdate: ${updateInfo.hasUpdate}'); //print('lxm---currentVersion: ${updateInfo.currentVersion}'); //print('lxm---latestVersion: ${updateInfo.latestVersion}'); //print('lxm---updateType: ${updateInfo.updateType}'); //print('lxm---appInfo: ${updateInfo.appInfo?.toJson()}'); if (!updateInfo.hasUpdate || updateInfo.appInfo == null) { return false; } try { _logger.d('lxm---执行更新操作,下载地址: ${updateInfo.appInfo!.address}'); return await _openDownloadUrl(updateInfo.appInfo!.address!); } catch (e) { _logger.e('执行更新失败: $e'); return false; } } /// 打开下载链接 Future _openDownloadUrl(String downloadUrl) async { return await _launchUrl(downloadUrl); } /// 启动URL Future _launchUrl(String url) async { try { final uri = Uri.parse(url); if (await canLaunchUrl(uri)) { return await launchUrl(uri, mode: LaunchMode.externalApplication); } return false; } catch (e) { _logger.e('启动URL失败: $e'); return false; } } }