初始化
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import '../../tools_base/banner/ads_grid_view_widget.dart';
|
||||
|
||||
/// 广告信息
|
||||
class AdsInfoModel {
|
||||
// ----- 基础信息 -----
|
||||
String? id;
|
||||
String? title;
|
||||
String? cover; //封面图
|
||||
String? href; //跳转地址
|
||||
String? newUrl;
|
||||
|
||||
// ----- 官方/来源信息 -----
|
||||
String? officialImg;
|
||||
String? officialName;
|
||||
String? officialDesc;
|
||||
String? officialUrl;
|
||||
String? officialType;
|
||||
|
||||
// ----- 展示位置与排序 -----
|
||||
String? positionName;
|
||||
String? imPosition; //IM 广告位标识(IM_LIST_BANNER 等字符串 key)
|
||||
/// 广告位置
|
||||
/// 0 - 启动页广告
|
||||
/// 1 - 首页-推荐显示小广告
|
||||
/// 2 - 首页-推荐公告展示
|
||||
/// 3 - 消息界面广告
|
||||
/// 4 - 首页-热点 banner广告
|
||||
int? position;
|
||||
|
||||
/// 同一位置使用此进行排序
|
||||
int? sortCode;
|
||||
|
||||
/// 广告样式
|
||||
/// full-screen 全屏(启动页、短视频)
|
||||
/// banner-720300 Banner720300
|
||||
/// banner-720150 Banner720150
|
||||
/// banner-750700 Banner750700
|
||||
/// banner-800450 Banner800450
|
||||
/// pop-ups-600800 弹窗600800
|
||||
/// pop-ups-512512 弹窗512512
|
||||
/// small-cube 小方块
|
||||
/// vertical-cube 竖方块
|
||||
String? coverImgSize;
|
||||
|
||||
// ----- 业务控制 -----
|
||||
int? moduleType; //楼凤广告模块过滤
|
||||
int? watchTime; //广告关闭时长
|
||||
|
||||
AdsInfoModel({this.id, this.cover, this.href, this.title});
|
||||
|
||||
/// 是否有广告倒计时
|
||||
bool get hasAdTime => (watchTime ?? 0) > 0;
|
||||
|
||||
/// 根据 coverImgSize 映射广告布局样式(small-cube 与未知值同走默认)
|
||||
AdStyle get adStyle => switch (coverImgSize) {
|
||||
'vertical-cube' => AdStyle.oneScrollBig,
|
||||
'banner-720300' => AdStyle.banner,
|
||||
_ => AdStyle.twoAutoScroll,
|
||||
};
|
||||
|
||||
//同时兼容 Adv(image/name/url) 与 ShuApp(icon/name/url) 字段
|
||||
AdsInfoModel.fromJson(dynamic json) {
|
||||
json ??= {};
|
||||
id = json['id'];
|
||||
title = json['title'] ?? json['name'];
|
||||
cover = json['cover'] ?? json['image'] ?? json['icon'];
|
||||
href = json['href'] ?? json['url'];
|
||||
newUrl = json['newUrl'];
|
||||
officialImg = json['officialImg'];
|
||||
officialName = json['officialName'];
|
||||
officialDesc = json['officialDesc'];
|
||||
officialUrl = json['officialUrl'];
|
||||
officialType = json['officialType'];
|
||||
positionName = json['positionName'];
|
||||
position = json['position'];
|
||||
sortCode = json['sortCode'];
|
||||
coverImgSize = json['coverImgSize'];
|
||||
moduleType = json['moduleType'];
|
||||
watchTime = json['watchTime'];
|
||||
}
|
||||
|
||||
//和 fromJson 同一套解析,别再各写一份
|
||||
static AdsInfoModel fromMap(Map<dynamic, dynamic>? map) => AdsInfoModel.fromJson(map);
|
||||
|
||||
Map toJson() => {
|
||||
"id": id,
|
||||
"title": title,
|
||||
"cover": cover,
|
||||
"href": href,
|
||||
"position": position,
|
||||
"sortCode": sortCode,
|
||||
"positionName": positionName,
|
||||
};
|
||||
}
|
||||
|
||||
/// 公告信息
|
||||
class AnnounceInfoBean {
|
||||
String? content;
|
||||
String? cover;
|
||||
String? href; //图片公告点击时跳转的地址
|
||||
int? type; //0:系统公告 1:图片公告 3:活动公告
|
||||
String? title;
|
||||
|
||||
static AnnounceInfoBean fromJson(Map<String, dynamic>? map) {
|
||||
map ??= {};
|
||||
return AnnounceInfoBean()
|
||||
..content = map['content']
|
||||
..cover = map['cover']
|
||||
..href = map['href']
|
||||
..type = map['type']
|
||||
..title = map['title'];
|
||||
}
|
||||
|
||||
Map toJson() => {
|
||||
"content": content,
|
||||
"cover": cover,
|
||||
"href": href,
|
||||
"type": type,
|
||||
"title": title,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/// 粘贴版信息
|
||||
class CutInfo {
|
||||
// pc是邀请码promoteCode
|
||||
String? pc;
|
||||
|
||||
/// dc是渠道 distinctCode
|
||||
String? dc;
|
||||
String? tid;
|
||||
|
||||
static CutInfo fromMap(Map<String, dynamic>? map) {
|
||||
map ??= {};
|
||||
return CutInfo()
|
||||
..pc = map['pc']
|
||||
..dc = map['dc']
|
||||
..tid = map['tid'];
|
||||
}
|
||||
|
||||
Map toJson() => {
|
||||
"pc": pc,
|
||||
"dc": dc,
|
||||
"tid": tid,
|
||||
}..removeWhere((key, value) => value == null || (value as String?)?.isEmpty == true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
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 卡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<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');
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
class WatchCount {
|
||||
int? watchCount; // 剩余免费观看次数
|
||||
int? total; // 免费观看总次数(后端下发,用于"剩余 x/y 次"展示)
|
||||
bool? isCan; // 金币视频不再免费次数内
|
||||
|
||||
//⚠️注意: coinWatchCount: 活动权益金币视频免费观看次数, 需要手动赋值,
|
||||
int coinWatchCount = -1; //短视频业务专用, 因为短视频业务提前预加载数据,需要copy对象,保存当前对应的count值
|
||||
|
||||
static WatchCount fromJson(Map<String, dynamic>? map) {
|
||||
map ??= {};
|
||||
return WatchCount()
|
||||
..watchCount = map['watchCount']
|
||||
..total = map['total']
|
||||
..isCan = map['isCan'];
|
||||
}
|
||||
|
||||
//只复制展示用的次数;coinWatchCount 按注释由调用方自行赋值
|
||||
WatchCount copy() => WatchCount()
|
||||
..watchCount = watchCount
|
||||
..total = total;
|
||||
}
|
||||
Reference in New Issue
Block a user