import 'dart:convert'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hgdj/config/address.dart'; import 'package:hgdj/config/config.dart'; import 'package:hgdj/hj_model/home/plate_model.dart'; import 'package:hgdj/hj_model/splash/ads_model.dart'; import 'package:hgdj/hj_model/splash/cut_info.dart'; import 'package:hgdj/hj_model/splash/domain_source_model.dart'; import 'package:hgdj/hj_model/user/user_info_model.dart'; import 'package:hgdj/hj_page/main_page/main_logic.dart'; import 'package:hgdj/hj_page/main_page/main_page.dart'; import 'package:hgdj/hj_page/pre_sale/limit_time_provider.dart'; import 'package:hgdj/hj_page/pre_sale/pre_sale_provider.dart'; import 'package:hgdj/hj_utils/api_service/common_service.dart'; import 'package:hgdj/hj_utils/detect_line_manager.dart'; import 'package:hgdj/hj_utils/local_server.dart'; import 'package:hgdj/hj_utils/local_server_guard.dart'; import 'package:hgdj/hj_utils/text_util.dart'; import 'package:hgdj/hj_utils/version_util.dart'; import 'package:hgdj/tools_base/ad_manager.dart'; import 'package:hgdj/tools_base/debug_log.dart'; import 'package:hgdj/tools_base/global_store/store.dart'; import 'package:hgdj/tools_base/net/api_exception.dart'; import 'package:hgdj/tools_base/net/net_manager.dart'; import 'package:hgdj/tools_base/widget/common_alert.dart'; import 'package:path/path.dart' as path; import '../../alert/splash/line_error_dialog.dart'; import '../../alert/splash/update_dialog.dart'; import '../../hj_utils/free_play_manager.dart'; import '../../tools_base/loading/loading_alert_widget.dart'; import '../../track_event_manager/device_service.dart'; import '../live/live_main_page.dart'; import '../main_page/provider/msg_provider.dart'; //启动页 class SplashLogic extends GetxController { // ===== 开屏广告 ===== List? adsList; //开屏广告,没有就直接过 bool isAdLoaded = false; //广告数据已就位(可能为空),页面据此决定要不要挂广告层 bool isAdDone = false; //广告已放完/跳过,只等数据就绪 // ===== 启动数据 ===== bool isDataReady = false; //模块列表已落地 bool _isExiting = false; //强更被拒、已请求退出 app:后续流程一律不再放行首页 @override onReady() { super.onReady(); _start(); } Future _start() async { netManager.clearUserAgent(); //清除老的ua await AdManager().preload(); //预热广告内存缓存,后续统一用 AdManager().adsByType _loadAds(isLast: true); //获取上次启动的广告 final line = await _detectLine(); //选线 //拿不到线路就到此为止:要么用户放弃了重试,要么弹框回调里重试成功、 //已经自己走了一遍 _initByLine。这里再调一次会重复初始化 if (TextUtil.isEmpty(line)) return; _initByLine(line!); //初始化流程 } //选到线路后的完整启动流程 Future _initByLine(String line) async { //1. 初始化网络层 _initNet(line); //2.先登录,后升级,不然渠道一升级就没有量了,先登录才能获取配置信息 await _devLogin(); // 模块列表只依赖登录拿到的 token,请求先发出去跟下面的配置/更新检查并行跑, // 但结果留到更新检查之后才放行(见末尾)——强更期间不能让首页变成可跳 // ignore():先占个监听位,免得错误在 await 到之前被当成未捕获异常上报;真正的错误照旧在 await 处抛 final tagTask = CommonService.getTagMarks()..ignore(); //2. 获取远程配置 final versions = await _fetchConfig(); PreSaleProvider().refreshConfig(); MineMsgProvider().startPayTimer(); //冷启动 /ping/domain 下发支付分层配置后启动倒计时 FreePlayManager().refresh(); //免费次数进首页才用得到,不 await、不挡启动 await _checkUpdate(versions); //强更时会停在启动页,必须挡在放行之前 if (_isExiting) return; //强更被拒,app 正在退出,别再往下放行 _initServer(); // 初始化localserver缓存 await _waitModules(tagTask); //放行跳首页 } ///isLast = true 取出上次展示的广告 Future _loadAds({bool isLast = true}) async { final list = isLast ? await AdManager().lastAdsByType(1) : AdManager().adsByType(1); if (AdManager().showAbTestAd) adsList = list; isAdLoaded = true; update(); } ///一直检查网络直到成功 Future _detectLine() async { LoadingAlertWidget.show(title: "选线中..."); final net = await Connectivity().checkConnectivity(); // 有网络,开始选线 if (net != ConnectivityResult.none) { final line = await DetectLineManager().detectLineOnce(); LoadingAlertWidget.cancel(); if (TextUtil.isNotEmpty(line)) return line; } await LineErrorDialog.show(callback: () async { final line = await DetectLineManager().detectLineOnce(); LoadingAlertWidget.cancel(); if (TextUtil.isNotEmpty(line)) _initByLine(line); //初始化流程 }); LoadingAlertWidget.cancel(); return null; } void _initNet(String host) { Address.baseHost = host; Address.baseApiPath = path.join(Address.baseHost ?? "", Address.apiPrefix); netManager.init(Address.baseApiPath ?? ""); } ///登录 Future _devLogin() async { String paste = ""; //2.获取渠道码 final channel = await DeviceInfoService.getChannel(); if (TextUtil.isNotEmpty(channel)) { final traceId = await DeviceInfoService.fetchTraceId(); final cutInfo = CutInfo() ..dc = channel ..tid = traceId; paste = json.encode(cutInfo.toJson()); } else { //1.获取粘贴板(走缓存,避免 iOS 多次弹系统读取提示) final clipText = await DeviceInfoService.getClipboardTextCached(); if (TextUtil.isNotEmpty(clipText)) { paste = clipText!; } } final deviceId = DeviceInfoService.deviceId; await netManager.refreshUserAgent(); //登录 + 重试 while (true) { final userInfo = await globalStore.loginByDevice(deviceId, paste: paste); if (userInfo != null) return userInfo; //免广告策略已在 globalStore 登录成功时算好 final retry = await CommonAlert.show( content: "登录失败,是否重试?", confirmText: '重试', ); if (retry != true) return null; } } //拉远端配置并落地,失败弹框重试;用户放弃就返回空版本列表(等于不检查更新) Future> _fetchConfig() async { List? versions; while (versions == null) { try { final info = await CommonService.fetchRemoteConfig(); if (info == null) { // 配置拉取失败(无 token / 网络异常 / 返回体异常),走下面 catch 的重试逻辑 throw Exception('获取远端配置为空'); } await _applyDomains(info); await AdManager().saveAds(info.ads?.adsInfoList); await _loadAds(isLast: false); Config.aiUndressPrice = info.aiUndressPrice ?? ''; Config.aiVideoPrice = info.aiImageToVideoPrice ?? ''; Config.aiDrawPrice = info.aiTextToImagePrice ?? ''; Config.aiNovelPrice = info.aiTextToNovelPrice ?? ''; Config.isStoreOpen = info.storeIsOpen ?? false; Config.jgArea = info.jgArea; versions = info.ver ?? []; //直播模块开关 LiveMainPage.broadcast = info.broadcast ?? false; //预售相关 presaleProvider.advanceStatus = info.advanceStatus; presaleProvider.advancePage = info.advancePage; //幸运抽奖相关 presaleProvider.luckyDrawH5 = info.luckyDrawH5; presaleProvider.luckyDrawIcon = info.luckyDrawIcon; //限时活动相关 limitTimeProvider.bannerJump = info.bannerJump; Config.proxyBanner = info.proxyBannerJump; Config.hotWords = info.hotSearchTerms ?? []; Config.searchHints = info.searchHintWord ?? []; Config.darkWebVipId = info.darkWebVipId; Config.darkWebVipName = info.darkWebVipName; Config.shortDramaCardId = info.shortDramaCardId; } catch (e) { debugLog('getRemoteConfig', e.toString()); final retry = await CommonAlert.show( content: "获取配置信息失败${(e is ApiException) ? e.code : 1500},是否重试?", confirmText: '重试', ); if (retry != true) break; } } return versions ?? []; } //域名/公告落地 Future _applyDomains(DomainSourceModel info) async { //域名列表信息 for (final source in info.sourceList!) { switch (source.type) { case "IMAGE": Address.baseImagePath = source.domain![0].url; break; case "VID": Address.cdnAddressLists = source.domain!; Address.cdnAddress = Address.cdnAddressLists[0].url; break; case "GUIDE": Address.groundUrl = source.domain![0].url; break; case 'AUDIO': Address.audioCdnAddress = source.domain?.firstOrNull?.url; break; } } AdManager().announceList = info.ads?.announList ?? []; } ///检查是否需要更新 Future _checkUpdate(List versions) async { //存内存,设置页的「检查更新」直接读这份比对,不再发请求 saveVersion(versions); final target = checkUpdate(); if (target == null) return; // 用户是否点了更新 final isUpdate = await UpdateDialog.show(target); if (isUpdate != true && target.forcedUpdate == true) { //SystemNavigator.pop 只 finish Activity,engine 还能活几百毫秒, //这期间后面的流程会照跑、够把首页放行出去,所以打标记让调用方直接收尾 _isExiting = true; await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } } //本地缓存服务:守护进程被内部 Timer 持有,不用再存字段 Future _initServer() async { final server = CacheServer(cacheManager: null, openSubManager: true); await LocalServerGuard(server).run(); //其余任意文件拦截 server.addReqFilter(LOCAL_ALL_FILTER, Address.baseImagePath!); } //模块数据落地 → 放行跳首页。请求在登录后就发出去了,这里只等结果。 //拿不到模块一律不放行:首页 tab 全靠它,宁可停在启动页重试,也不进只剩"最新"的降级首页。 //弹框只给"确定"一个选择,且不判返回值——点遮罩关掉也照样重试,不给静默卡死的出口 Future _waitModules(Future task) async { var pending = task; while (true) { HomePlateModel? model; try { model = await pending; } catch (e) { //网络异常和 fromJson 异常都被网络层吞成 null,走不到这;兜的是请求前置(取token/签名)抛的异常 debugLog('getTagMarks', e.toString()); } if (model != null) { Config.plateModule = model; isDataReady = true; _tryJump(); return; } await CommonAlert.show(content: "获取模块信息失败,请重试", showCancel: false); pending = CommonService.getTagMarks(); //旧 Future 已完成,重试必须重新发请求 } } //广告结束:标记可跳转、刷新出过渡遮罩,再尝试进首页 void onAdFinish() { isAdDone = true; update(); _tryJump(); } //广告和数据两边都就绪才进首页 void _tryJump() { //opaque 在 offAll 里默认 false,不显式打开会在转场时透出下层 if (isAdDone && isDataReady) Get.offAll(() => const MainPage(), binding: MainPageBinding(), opaque: true); } }