@@ -0,0 +1,543 @@
import ' package:jydj/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 ; //原味商店开关
String ? festivalUi ; //节日ui
List < JGAreaModel > ? jgArea ; //金刚区配置
String ? reportSdkConfig ;
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 ; //短剧引导气泡,字段缺失当开启
Config . isDramaEntry = map [ ' defaultEntryPage ' ] = = ' drama ' ; //默认进哪个 tab,缺字段/其它值都按主页
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 . festivalUi = map [ ' festivalUi ' ] ;
if ( info . festivalUi ? . isNotEmpty ! = true ) info . festivalUi = ' 默认 ' ;
// 抽奖
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 . reportSdkConfig = map [ ' reportSdkConfig ' ] ;
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 ' ) ;
}