import 'package:hgdj/assets_tool/images.dart'; import '../../alert/vip_guide/guide_manager.dart'; import '../../config/config.dart'; import '../../hj_page/ai/ai_sub_type/ai_function_logic.dart'; import '../../hj_page/main_page/provider/msg_provider.dart'; import '../../hj_page/pre_sale/pre_sale_model.dart'; import 'ads_model.dart'; /// 预售/活动状态 class AdvanceStatus { bool? activityPopUp; //预售活动是否首页弹窗 bool? activityStatus; //是否开启 bool? balancePayment; //是否有尾款支付 String? oid; PrivilegeLimit? privilegeLimit; //预付权益 AdvanceStatus.fromJson(dynamic json) { activityStatus = json['activityStatus']; balancePayment = json['balancePayment']; activityPopUp = json['activityPopUp']; oid = json['oid']; privilegeLimit = json['privilegeLimit'] != null ? PrivilegeLimit.fromJson(json['privilegeLimit']) : null; } } /// 预付权益限制与剩余次数 class PrivilegeLimit { bool? hasLimit; //是否有权益限制 Remain? remain; PrivilegeLimit({this.hasLimit, this.remain}); PrivilegeLimit.fromJson(Map json) { hasLimit = json['hasLimit']; remain = json['remain'] != null ? Remain.fromJson(json['remain']) : null; } } /// 今日各项功能剩余次数 class Remain { int? todayAiUndressCount; //今日剩余ai脱衣次数 int? todayCoinVideoCount; //今日剩余金币视频观看次数 int? todayDownloadCount; //今日剩余下载次数 Remain( {this.todayAiUndressCount, this.todayCoinVideoCount, this.todayDownloadCount}); Remain.fromJson(Map json) { todayAiUndressCount = json['todayAiUndressCount']; todayCoinVideoCount = json['todayCoinVideoCount']; todayDownloadCount = json['todayDownloadCount']; } void reduceVideoCoinCount() { todayCoinVideoCount = (todayCoinVideoCount ?? 0) - 1; } } /// 同一份 bannerJump 下按位置取:key '1' 播放页,'2' 全民代理页 class BannerJump { AdditionalProp? additionalProp; BannerJump({this.additionalProp}); BannerJump.fromJson(Map json, {String key = '1'}) { additionalProp = json[key] != null ? AdditionalProp.fromJson(json[key]) : null; } } class AdditionalProp { String? id; String? title; //标题 String? banner; //BANNER图片 String? url; //跳转地址 int? position; //位置 1:视频播放器下方 String? startAt; //开始时间 String? endAt; //结束时间 int? countdownType; AdditionalProp( {this.id, this.banner, this.endAt, this.position, this.startAt, this.title, this.url, this.countdownType}); AdditionalProp.fromJson(Map json) { id = json['id']; title = json['title']; banner = json['banner']; url = json['url']; position = json['position']; startAt = json['startAt']; endAt = json['endAt']; countdownType = json['countdownType']; } Map toJson() => { 'id': id, 'title': title, 'banner': banner, 'url': url, 'position': position, 'startAt': startAt, 'endAt': endAt, 'countdownType': countdownType, }; } /// 全局配置:域名、广告、AI价格、活动、搜索等 class DomainSourceModel { // ----- 域名 / 线路 / 版本 ----- List? domain; //接口域名 List? sourceList; List? ver; // ----- 广告 / 公告 ----- AdsBean? ads; // ----- AI 功能价格 ----- String? aiUndressPrice; //AI脱衣价格 String? aiImageToVideoPrice; //图生视频价格 String? aiTextToImagePrice; //绘图价格 String? aiTextToNovelPrice; //小说价格 // ----- 活动 / 预售 / Banner ----- AdvanceStatus? advanceStatus; AdvancePage? advancePage; BannerJump? bannerJump; //限时Banner活动 BannerJump? proxyBannerJump; // ----- 抽奖 ----- String? luckyDrawH5; //抽奖H5链接 String? luckyDrawIcon; //抽奖入口浮动图标 // ----- 搜索 ----- List? hotSearchTerms; //搜索热门词 List? searchHintWord; //搜索提示词 // ----- 暗网 VIP ----- String? darkWebVipId; //暗网vip卡id String? darkWebVipName; //暗网vip卡名 String? shortDramaCardId; //短剧卡id(付费墙默认选中的会员卡) List? recommendVipIds; // ----- 其他 ----- bool? broadcast; //是否展示直播模块 bool? storeIsOpen; //原味商店开关 List? jgArea; //金刚区配置 static DomainSourceModel fromJson(Map? map) { map ??= {}; final info = DomainSourceModel(); // 直接写入全局 Config 的配置项 Config.vipMark = map['vipMark'] ?? true; Config.coinMark = map['coinMark'] ?? true; Config.freeMark = map['freeMark'] ?? true; Config.signIcon = map['signIcon']; Config.darkWebEnable = map['darkWebEnable'] ?? true; Config.darkWebImg = map['darkWebImg']; Config.darkWebIcon = map['darkWebIcon']; Config.darkWebIconName = map['darkWebIconName']; Config.mineBg = map['personalCenterBackground']; Config.entryPopupEnabled = map['shortDramaEntryPopupEnabled'] ?? true; //短剧引导气泡,字段缺失当开启 MineMsgProvider().payTier = PayTierModel.fromJson(map); //支付状态分层(各展示位图片/卡ID/倒计时) GuideManager().update(map['paymentGuide']); //付费引导各场景开关(弹窗一律读缓存,不再临时查接口) if (map['aiSwitchConf'] is List) { Config.aiTypes = (map['aiSwitchConf'] as List) .map((e) => AISwitchConf.fromJson(e)) .toList(); } if (map.containsKey('bannerJumpList')) { Config.bannerJumps = (map['bannerJumpList'] as List?) ?.map((o) => BannerJumpEntity.fromJson(o)) .toList() ?? []; } // 域名 / 线路 / 版本 if (map.containsKey('domain')) { info.domain = (map['domain'] as List?)?.map((o) => o.toString()).toList(); } if (map.containsKey('sourceList')) { info.sourceList = (map['sourceList'] as List?) ?.map((o) => Sourcelist.fromJson(o)) .toList(); } if (map.containsKey('ver')) { info.ver = (map['ver'] as List?) ?.map((o) => CheckVersionInfo.fromMap(o)) .toList(); } // 广告 if (map.containsKey('ads')) { info.ads = AdsBean.fromMap(map['ads']); } // AI 价格 info.aiUndressPrice = map['aiUndressPrice']?.toString(); info.aiImageToVideoPrice = map['aiImageToVideoPrice']?.toString(); info.aiTextToImagePrice = map['aiTextToImagePrice']?.toString(); info.aiTextToNovelPrice = map['aiTextToNovelPrice']?.toString(); // 活动 / 预售 / Banner if (map.containsKey('advancePage')) { info.advancePage = AdvancePage.fromJson(map['advancePage']); } if (map.containsKey('advanceStatus')) { info.advanceStatus = AdvanceStatus.fromJson(map['advanceStatus']); } info.bannerJump = map['bannerJump'] != null ? BannerJump.fromJson(map['bannerJump']) : null; info.proxyBannerJump = map['bannerJump'] != null ? BannerJump.fromJson(map['bannerJump'], key: '2') : null; // 抽奖 info.luckyDrawH5 = map['luckyDrawH5']; info.luckyDrawIcon = map['luckyDrawIcon']; // 搜索 if (map['hotSearchTerms'] is List) { info.hotSearchTerms = (map['hotSearchTerms'] as List).map((e) => e.toString()).toList(); } if (map['searchHintWord'] is List) { info.searchHintWord = (map['searchHintWord'] as List).map((e) => e.toString()).toList(); } // 暗网 VIP info.darkWebVipId = map['darkWebVipId']; info.darkWebVipName = map['darkWebVipName']; info.shortDramaCardId = map['shortDramaCardId']; if (map['recommendVipIds'] is List) { info.recommendVipIds = (map['recommendVipIds'] as List).map((e) => e.toString()).toList(); } // 其他 info.broadcast = map['broadcast'] ?? false; info.storeIsOpen = map['storeIsOpen']; if (map['jgArea'] is List) { info.jgArea = (map['jgArea'] as List?) ?.map((e) => JGAreaModel.fromJson(e)) .toList(); } return info; } } /// 跑马灯公告 class MarqueeModel { String? content; //跑马灯内容 String? url; //跳转连接 bool? active; //激活状态 String? id; //公告id int? type; //跑马灯类型,0:会员中心 int? position; String? positionDesc; static MarqueeModel fromMap(Map? map) { map ??= {}; return MarqueeModel() ..content = map['content'] ..url = map['url'] ..active = map['active'] ..id = map['id'] ..type = map['type'] ..position = map['position'] ..positionDesc = map['positionDesc']; } } /// 版本检测信息 class CheckVersionInfo { String? description; bool? forcedUpdate; //是否强制更新 String? platform; String? url; //Android 的 apk 下载地址 String? iosUrl; //iOS 的 App Store 地址,点升级直接跳商店 String? verName; int? code; static CheckVersionInfo fromMap(Map? map) { map ??= {}; return CheckVersionInfo() ..description = map['description'] ..forcedUpdate = map['forcedUpdate'] ..platform = map['platform'] ..url = map['url'] ..iosUrl = map['iosUrl'] ..verName = map['verName'] ..code = map['Code']; } } /// 广告聚合:图片广告列表 + 文本公告 class AdsBean { List? adsInfoList; List? announInfo; //文本公告 ///多个公告 List? announList; static AdsBean fromMap(Map? map) { map ??= {}; return AdsBean() ..announInfo = (map['announInfo'] as List?) ?.map((o) => AnnounceInfoBean.fromJson(o)) .toList() ..announList = (map['announList'] as List?) ?.map((o) => AnnounceInfoBean.fromJson(o)) .toList() ..adsInfoList = (map['adsInfoList'] as List?) ?.map((o) => AdsInfoModel.fromMap(o)) .toList(); } Map toJson() => { "announInfo": announInfo, }; } /// 金刚区单项 class JGAreaModel { String? id; String? name; int? type; String? img; String? desc; int? linkType; String? linkUrl; String? mid; JGAreaModel( {this.id, this.name, this.img, this.desc, this.linkType, this.linkUrl, this.type, this.mid}); factory JGAreaModel.fromJson(Map json) => JGAreaModel( id: json["id"], name: json["name"], img: json["img"], desc: json["desc"], linkType: json["link_type"], linkUrl: json["link_url"], type: json["type"], mid: json["mid"], ); Map toJson() => { "id": id, "name": name, "img": img, "desc": desc, "link_type": linkType, "link_url": linkUrl, 'mid': mid, }; } /// 线路源 class Sourcelist { String? id; List? domain; bool? isActive; String? type; Sourcelist({this.id, this.domain, this.isActive, this.type}); factory Sourcelist.fromJson(Map json) => Sourcelist( id: json["id"], domain: List.from(json["domain"].map((x) => Domain.fromJson(x))), isActive: json["isActive"], type: json["type"], ); Map toJson() => { "id": id, "domain": List.from(domain!.map((x) => x.toJson())), "isActive": isActive, "type": type, }; } /// 域名节点(带权重) class Domain { int? weight; String? url; String? desc; int? status; Domain({this.weight, this.url, this.desc, this.status}); factory Domain.fromJson(Map json) => Domain( weight: json["weight"], url: json["url"], desc: json["desc"], status: json["status"], ); Map toJson() => { "weight": weight, "url": url, "desc": desc, "status": status, }; } //AI 类型配置表:type → 名称/功能枚举/图标(存图标名,读取时拼 .aiPath) class _AiTypeConf { final String name; final AiType type; final String img; const _AiTypeConf(this.name, this.type, this.img); } const _aiTypeConfMap = { 1: _AiTypeConf('AI脱衣', AiType.autoStrip, 'ai_address.png'), 2: _AiTypeConf('视频换脸', AiType.videoChangeFace, 'ai_video_face.png'), 3: _AiTypeConf('图片换脸', AiType.imageChangeFace, 'ai_pic_face.png'), 4: _AiTypeConf('图生视频', AiType.imageToVideo, 'ai_itv.png'), 5: _AiTypeConf('AI绘画', AiType.aiPaint, 'ai_draw.png'), 6: _AiTypeConf('AI小说', AiType.aiNovel, 'ai_novel_enter.png'), 7: _AiTypeConf('AI女友', AiType.aiMate, 'ai_mate_enter.png'), }; /// AI 功能开关配置 class AISwitchConf { int? type; //AI类型 1:AI脱衣 2:AI视频换脸 3:AI图片换脸 4:图生视频 5:文生图 6:AI小说 int? sort; //排序,越小越靠前 bool? isOpen; //是否开启 String? aiTypeName; String? img; //图标(选中/未选中同一张,渲染时统一染白) AiType? aiType; AISwitchConf.fromJson(Map json) { type = json['type']; sort = json['sort']; isOpen = json['isOpen']; final conf = _aiTypeConfMap[type]; if (conf != null) { aiTypeName = conf.name; aiType = conf.type; img = conf.img.aiPath; } } } /// 限时 Banner 跳转配置 class BannerJumpEntity { String? id; int? position; String? url; String? title; String? startAt; String? banner; String? endAt; int? countdownType; BannerJumpEntity( {this.title, this.id, this.position, this.url, this.startAt, this.banner, this.endAt, this.countdownType}); BannerJumpEntity.fromJson(dynamic json) { id = json['id']; position = json['position']; url = json['url']; title = json['title']; startAt = json['startAt']; endAt = json['endAt']; banner = json['banner']; countdownType = json['countdownType']; } } /// 支付状态分层(ping/domain 的 paymentStatusPopup 字段),用于付费流程的用户分层 enum PayTier { newUnpay('new_unpay'), //新用户:注册≤1天且从未付费 → 推新人200卡(新人价+首购引导+紧迫感) under7DayUnpay('under_7_day_unpay'), //未付费·注册1~7天 → 推300卡(限时优惠+紧迫感) over7DayUnpay('over_7_day_unpay'), //未付费·注册>7天 → 推300卡(永久卡唤醒需求+信任背书) over7DayNeedUpgrade( 'over_7_day_need_upgrade'), //低级会员需续费:注册>7天且已付费、未达最高等级(<5) → 推500至尊卡 normal('normal'), //正常用户:无特殊弹窗 unregistered('unregistered'); //未注册(未登录) final String value; const PayTier(this.value); static PayTier? from(String? value) { if (value == null || value.isEmpty) return null; for (final e in PayTier.values) { if (e.value == value) return e; } return null; } } /// 支付状态弹窗配置(收敛 ping/domain 支付分层相关字段) class PayTierModel { PayTier? status; //用户分层 PayTierConfig? config; //各展示位配置(图片/卡ID/倒计时) PayTierModel({this.status, this.config}); PayTierModel.fromJson(Map? json) { json ??= {}; status = PayTier.from(json['paymentStatusPopup']); // /ping/domain 配置在 paymentStatusPopupConfig 下;/ping/domain/refresh 平铺在外层,回退用 json 本身 config = PayTierConfig.fromJson(json['paymentStatusPopupConfig'] ?? json); } } /// 支付状态弹窗各展示位配置(meTab/playPage 为图片链接;vipCard 卡ID;lastDiscountTime 优惠截止时间) /// 全站新人卡/分层优惠倒计时的唯一数据源,各展示位一律读这里,不再另开接口取时间 class PayTierConfig { String? homePage; //首页弹窗图片链接 String? homePageFlot; //首页浮窗图片链接 String? playPage; //播放页图片链接 String? meTab; //me tab 图片链接 String? vipCard; //点击跳转的会员卡 ID int? lastDiscountTime; //优惠截止 Unix 时间戳(秒),早于当前则不展示倒计时 PayTierConfig( {this.homePage, this.homePageFlot, this.playPage, this.meTab, this.vipCard, this.lastDiscountTime}); PayTierConfig.fromJson(Map? json) { json ??= {}; homePage = json['homepage']; homePageFlot = json['homepageFlot']; playPage = json['playPage']; meTab = json['meTab']; vipCard = json['vipCard']; lastDiscountTime = _parseEpochSec(json['lastDiscountTime']); } static const _minValidEpochSec = 946684800; //2000-01-01,用于挡掉后端零值时间 0001-01-01T00:00:00Z //兼容 Unix 秒(int) / 数字字符串 / ISO 字符串(带 Z 或 +08:00 时区);零值哨兵一律归 null,避免负 epoch 流入业务 static int? _parseEpochSec(dynamic v) { if (v == null) return null; int? sec; if (v is num) { sec = v.toInt(); } else if (v is String) { final s = v.trim(); if (s.isEmpty) return null; sec = int.tryParse(s) ?? (DateTime.tryParse(s)?.millisecondsSinceEpoch ?? 0) ~/ 1000; } return (sec != null && sec >= _minValidEpochSec) ? sec : null; } /// 优惠剩余秒数(截止时刻 - 当前),≤0 表示已过期/不展示倒计时 int get discountRemainSec { final end = lastDiscountTime ?? 0; if (end <= 0) return 0; final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; final r = end - now; return r > 0 ? r : 0; } bool get hasDiscountCountdown => discountRemainSec > 0; String get discountHour => (discountRemainSec ~/ 3600).toString().padLeft(2, '0'); String get discountMin => ((discountRemainSec ~/ 60) % 60).toString().padLeft(2, '0'); String get discountSec => (discountRemainSec % 60).toString().padLeft(2, '0'); }