Files
2026-09-15 15:44:13 +07:00

210 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgdj/hj_model/splash/ads_model.dart';
import 'package:hgdj/hj_page/main_page/provider/msg_provider.dart';
import 'package:hgdj/hj_page/pre_sale/pre_sale_provider.dart';
import 'package:hgdj/alert/vip_guide/guide_manager.dart';
import 'package:hgdj/alert/vip_guide/timed_popup_manager.dart';
import 'package:hgdj/tools_base/ad_manager.dart';
import '../../alert/splash/app_center_dialog.dart';
import '../../alert/splash/notice_image_dialog.dart';
import '../../alert/splash/notice_text_dialog.dart';
import '../../alert/vip_guide/guide_bottom_sheet.dart';
import '../../alert/vip_guide/guide_home_dialog.dart';
import '../../config/config.dart';
import '../../hj_model/home/plate_model.dart';
import '../../tools_base/debug_log.dart';
import '../../tools_base/module_sort_manager.dart';
import '../mine/mine_vip/mine_charge_vip_page.dart';
import '../mine/mine_vip/pay_order_source.dart';
import '../mine/widgets/gradient_text.dart';
import '../pre_sale/limit_time_provider.dart';
class HomeMainLogic extends GetxController {
final bool isDarkStyle;
final String? darkDefaultId; // 暗网跳转到指定的tab使用
HomeMainLogic({
this.isDarkStyle = false,
this.darkDefaultId,
});
//开屏弹窗链每次启动只跑一次
static bool _adShown = false;
bool isLoading = true;
int tabViewKey = 0; //排序变了就 +1 换掉 HomeTabView 的 key,强制重建 TabController
int _initIndex = 0; // 注意⚠️:暗网进入,有可能通过内链指定亚模块进入,index通过defaultId 处理
//合并本地排序后的 tab 列表,_loadTabs 里算一次
List<ModuleData> tabs = [];
final scaffoldKey = GlobalKey<ScaffoldState>();
//tabCtr 由 HomeTabView 在自身 vsync 下创建并 dispose,这里只持引用驱动 tab 切换;排序时置 null 触发重建
TabController? tabCtr;
String get _sortKey => "_localSortKey_Tab_ID_cache_$isDarkStyle";
int get selectedTabIndex => tabCtr?.index ?? _initIndex;
//服务端下发的原始顺序(抽屉「恢复默认」用)
List<ModuleData> get defaultList {
if (isDarkStyle) return Config.plateModule?.deepWeb ?? [];
// 固定最新模块
return [
ModuleData(id: "-110", moduleName: "最新", type: 1, showType: 1),
...?Config.plateModule?.homePage,
];
}
@override
void onInit() {
super.onInit();
_loadTabs();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
limitTimeProvider.refresh(); //获取限时活动(放首帧后,避免 build 中 notifyListeners)
_showAdChain();
});
}
void openEndDrawer() => scaffoldKey.currentState?.openEndDrawer();
void gotoTabId(String tagDataId) {
final index = tabs.indexWhere((e) => e.id == tagDataId);
if (index >= 0) tabCtr?.index = index;
}
/// 应用抽屉里的新排序:顺序没变只切 tab,变了就落盘并换 key 重建 TabView
Future<void> resortTabs(List<ModuleData> dataArr, ModuleData? tagData) async {
final curTagData = tagData ?? tabs[selectedTabIndex];
bool isSameArr = true;
int selectIndex = 0;
//抽屉只重排不增删,长度必然一致;逐项比名字,顺带定位选中项的新位置
if (dataArr.length == tabs.length) {
for (int i = 0; i < dataArr.length; i++) {
if (dataArr[i].moduleName != tabs[i].moduleName) isSameArr = false;
if (dataArr[i].moduleName == curTagData.moduleName) selectIndex = i;
}
}
if (isSameArr) {
tabCtr?.index = selectIndex;
return;
}
await ModuleSortManager().saveLocalData(dataArr, _sortKey);
tabViewKey++;
tabs = dataArr;
_initIndex = selectIndex;
tabCtr = null; //旧 State 即将销毁,先断引用,由新 State 重新赋值
update();
}
//合并本地排序与服务端 tab,顺带算好初始选中的 index
Future<void> _loadTabs() async {
try {
tabs = await ModuleSortManager().mergeLocalSortWithServer(
serverTabs: defaultList,
key: _sortKey,
);
if (darkDefaultId?.isNotEmpty == true) {
// 初始化指定模块(一般暗网模块用)
final index = tabs.indexWhere((e) => e.id == darkDefaultId);
if (index >= 0) _initIndex = index;
} else if (!isDarkStyle) {
//默认不显示最新模块,选中「热门」(index 1);但 tab 不足 2 个时回退到 0,避免越界
_initIndex = (tabs.first.moduleName == "最新" && tabs.length > 1) ? 1 : 0;
}
} catch (e) {
debugLog(e);
}
isLoading = false;
update();
}
/// 首页开屏弹窗链:系统提示 → 图片公告 → 文字公告 → 预售 → 支付分层 → VIP 引导。
/// 逐个 await 串行弹,每次启动只跑一次
Future<void> _showAdChain() async {
if (_adShown) return;
_adShown = true;
if (AdManager().showAbTestAd) {
await _showAppDialog(); //系统提示
await _showImageDialog(); //图片公告
await _showWordDialog(); //文字公告
}
await PreSaleProvider().showAlert(); //预售弹窗
await _showPayDialog(); //支付分层首页弹窗
await _showVipGuide(); //首页吸底 VIP 引导(全链优先级最后)
}
/// 支付分层首页弹窗:有 homePage 图就弹,倒计时由 lastDiscountTime 自行判断。
/// 点海报跳会员中心,这里 await 到用户从会员页返回,否则链条后面的吸底引导会弹在会员页上
Future<void> _showPayDialog() async {
final config = MineMsgProvider().payTier?.config;
if (config == null || (config.homePage ?? '').isEmpty) return;
final goVip = await Get.dialog<bool>(GuideHomeDialog(config: config));
if (goVip != true) return;
await Get.to(() => MineChargeVipPage(
vipID: config.vipCard, sourcePage: PaySourcePage.homeUserSegment));
}
/// 首页吸底 VIP 引导:开屏弹窗链末尾弹(全链最低优先级),能不能弹由 HOME_NEW_USER / HOME_OLD_USER
/// 两个场景开关决定(后端按分层下发,最多命中一个);文案优先用下发的,没配走默认。
/// 走 TimedPopupManager 与其它引导共用互斥;每次启动只弹一次(_showAdChain 由静态 _adShown 保证)。
Future<void> _showVipGuide() async {
final scene = GuideManager().homeScene;
if (scene == null) return;
final config = GuideManager().configOf(scene);
await TimedPopupManager().trigger(
canShow: () => GuideManager().canShow(scene),
onShow: () => GuideBottomSheet.show(
scene: scene,
title: Text(
config?.title?.isNotEmpty == true ? config!.title! : 'VIP影片免费试看',
style: const TextStyle(
color: Colors.white, fontSize: 19, fontWeight: FontWeight.w600),
),
subtitle: GradientText(
config?.description?.isNotEmpty == true
? config!.description!
: '开通会员 查看完整影片',
gradient:
const LinearGradient(colors: [Colors.white, Color(0xffEFD394)]),
style: const TextStyle(
color: Colors.white, fontSize: 15, fontWeight: FontWeight.w500),
),
),
);
}
///显示公告信息(9宫格应用广告)
Future<void> _showAppDialog() async {
final appList = AdManager().adsByType(3);
if (appList.isEmpty) return;
await Get.dialog(AppCenterDialog(appList: appList));
}
///显示图片公告(上下双排,每次弹两张)
Future<void> _showImageDialog() async {
final adsList = AdManager().adsByType(46);
for (int i = 0; i < adsList.length; i += 2) {
await Get.dialog(
SystemImagesDialog(ads: adsList.skip(i).take(2).toList()));
}
}
//文字公告
Future<void> _showWordDialog() async {
for (final wordBean in AdManager().announceList) {
await Get.dialog(
wordBean.type == 0
? NoticeTextDialog(
content: wordBean.content.toString(), wordBean: wordBean)
: NoticeImageDialog(
model:
AdsInfoModel(cover: wordBean.cover, href: wordBean.href)),
);
}
}
}