Files
huangguo_android/lib/hj_model/splash/domain_source_model.dart
T
2026-09-15 15:44:13 +07:00

606 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() => {
'id': id,
'title': title,
'banner': banner,
'url': url,
'position': position,
'startAt': startAt,
'endAt': endAt,
'countdownType': countdownType,
};
}
/// 全局配置:域名、广告、AI价格、活动、搜索等
class DomainSourceModel {
// ----- 域名 / 线路 / 版本 -----
List<String>? domain; //接口域名
List<Sourcelist>? sourceList;
List<CheckVersionInfo>? 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<String>? hotSearchTerms; //搜索热门词
List<String>? searchHintWord; //搜索提示词
// ----- 暗网 VIP -----
String? darkWebVipId; //暗网vip卡id
String? darkWebVipName; //暗网vip卡名
String? shortDramaCardId; //短剧卡id(付费墙默认选中的会员卡)
List<String>? recommendVipIds;
// ----- 其他 -----
bool? broadcast; //是否展示直播模块
bool? storeIsOpen; //原味商店开关
List<JGAreaModel>? jgArea; //金刚区配置
static DomainSourceModel fromJson(Map<String, dynamic>? 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<String, dynamic>? 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<String, dynamic>? 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<AdsInfoModel>? adsInfoList;
List<AnnounceInfoBean>? announInfo; //文本公告
///多个公告
List<AnnounceInfoBean>? announList;
static AdsBean fromMap(Map<String, dynamic>? 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<String, dynamic> 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<String, dynamic> toJson() => {
"id": id,
"name": name,
"img": img,
"desc": desc,
"link_type": linkType,
"link_url": linkUrl,
'mid': mid,
};
}
/// 线路源
class Sourcelist {
String? id;
List<Domain>? domain;
bool? isActive;
String? type;
Sourcelist({this.id, this.domain, this.isActive, this.type});
factory Sourcelist.fromJson(Map<String, dynamic> json) => Sourcelist(
id: json["id"],
domain:
List<Domain>.from(json["domain"].map((x) => Domain.fromJson(x))),
isActive: json["isActive"],
type: json["type"],
);
Map<String, dynamic> toJson() => {
"id": id,
"domain": List<dynamic>.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<String, dynamic> json) => Domain(
weight: json["weight"],
url: json["url"],
desc: json["desc"],
status: json["status"],
);
Map<String, dynamic> 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 = <int, _AiTypeConf>{
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<String, dynamic> 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<String, dynamic>? json) {
json ??= {};
status = PayTier.from(json['paymentStatusPopup']);
// /ping/domain 配置在 paymentStatusPopupConfig 下;/ping/domain/refresh 平铺在外层,回退用 json 本身
config = PayTierConfig.fromJson(json['paymentStatusPopupConfig'] ?? json);
}
}
/// 支付状态弹窗各展示位配置(meTab/playPage 为图片链接;vipCard 卡IDlastDiscountTime 优惠截止时间)
/// 全站新人卡/分层优惠倒计时的唯一数据源,各展示位一律读这里,不再另开接口取时间
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<String, dynamic>? 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');
}