import 'package:flutter/material.dart'; import 'package:hgdj/assets_tool/app_colors.dart'; import 'package:hgdj/hj_model/splash/ads_model.dart'; import 'package:hgdj/hj_model/video_model.dart'; import 'package:hgdj/tools_base/global_store/store.dart'; import 'acg/comic_chapters_model.dart'; import 'actress_info.dart'; import 'media_content.dart'; class CartoonMediaInfo { // —— 基础信息 —— String? id; //文档id String? title; //标题 String? author; //作者 String? summary; //简介 String? mediaType; //媒体类型 "video":动漫,"image":漫画 "text":小说 int? mediaSubType; //子类型 ,暂时只有小说使用 0-默认文本小说 1-有声小说 int? kind; //种类(1、哩番,2、3D,3、同人动画,4、单行本,5、连载漫画,6、韩漫 ) int? status; //状态 VidStatus? mediaStatus; //媒体状态 String? lsjId; // 老司机媒体资源ID // —— 封面 —— String? horizontalCover; //横版封面 String? verticalCover; //竖版封面 // —— 集数 / 更新 —— int? totalEpisode; //总集数 int? currentEpisode; //当前集数 int? freeEpisode; //免费集数 bool isCartoonFreeEpisode = false; //当前播放集是否属于免费集(前 freeEpisode 集):运行时按当前集号计算,命中则不拦播、进度可全程拖 int? updateStatus; //更新状态 0 默认 1、更新中 2、已完结 String? updateTime; //文档更新时间 String? createdAt; //文档创建时间 // —— 价格 / 权限 —— int? price; //价格(整部购买的类型 小说 cos 写真) int? discountPrice; // 折扣价(后端字段,App 未使用) int? contentsPrice; //所有子集的价格 int? permission; // 0:会员 1:金币购买 2:免费 bool? permissionIconHide; //售卖类型标识隐藏 // —— 专题 / 模块 —— String? mId; //模块ID String? moduleName; //模块名称 String? sId; //专题id String? sectionName; //专题名称 int? sectionSort; //专题排序 int? sortCode; //排序 // —— 统计 —— int? countBrowse; //浏览数 int? countCollect; //收藏数 int? countDisLike; //不喜欢数 int? countComment; //评论数 int? countLike; //喜欢数 int? countPurchases; //购买数 int? countView; //展现数 bool? hasFollow; //是否关注 // —— 标签 / 演员 / 内容 —— List? tagDetails; //标签对象 List? allActress; // 演员列表 ActressInfo? actress; // 主演员 List? contents; // 子集内容列表 AdsInfoModel? randomAdsInfo; // 随机广告 // —— 其他后端字段 —— int? direction; // 方向(横/竖) int? freeTime; // 免费试看时长 bool? isDelete; // 是否已删除 int? choiceSort; // 后端字段,App 未使用 int? countPurchasesRate; // 后端字段,App 未使用 String? updatedAct; // 后端字段,App 未使用 // —— 本地 / UI 字段(非后端返回) —— List? episodeList; // 通过接口获取,外部赋值(改版了,不是同一个接口返回) int? episodeCurPage; // 当前页码 int episodePageSize = 40; // 每页集数 int isUpSort = 0; //0-正序 1-倒序 bool haxNextEpisode = true; // 是否还有下一页 int? episodeNumber; //本地添加字段。用于判断,跳转到播放详情的index bool isSelected = false; // 是否选中 String? heroTag; //本地字段,用于传递hero动画tag 随机数 String? get coverH => horizontalCover?.isNotEmpty == true ? horizontalCover : verticalCover; String? get coverV => verticalCover?.isNotEmpty == true ? verticalCover : horizontalCover; String get unit => mediaType == 'image' ? '话' : '章'; //更新状态 String get updateDesc => updateStatus == 1 ? '连载中' : '已完结'; String get tagIds => tagDetails?.map((e) => e.id).toList().join(',') ?? ''; String get tagNames => tagDetails?.map((e) => e.name).toList().join(',') ?? ''; //更新状态 String get getDetailUpdateString { if (updateStatus == 2 || updateStatus == 0) { return '已完结'; } else { return '连载中'; } } Color get getDetailUpdateColor { if (updateStatus == 2 || updateStatus == 0) { return Color(0xff989898); } else { return AppColors.actionRed; } } //是否是有声小说 bool get isAudiobooks => mediaType == 'text' && mediaSubType == 1; //集数状态描述 String get episodeNumberStatus { if (updateStatus == 1) { if (currentEpisode == 0) { return '共$totalEpisode$unit'; } else { return '更新$currentEpisode$unit'; } } else { return '共$totalEpisode$unit'; } } //评论类型,传参数类型 String get commentType => 'cartoon'; /// 该集是否落在前 N 集免费区间内。 /// 集号缺失兜底为 1(与项目其他处 `episodeNumber ?? 1` 口径一致);服务端真给 0 或负数时判为非免费 bool isFreeEpisodeNumber(int? episodeNumber) { final ep = episodeNumber ?? 1; return ep >= 1 && (freeEpisode ?? 0) >= ep; } /// 转成播放器用的 [VideoModel],[episode] 为当前集 /// 顺带把 [isCartoonFreeEpisode] 按当前集号刷新一次(有副作用,别当纯转换用) VideoModel toVideoModel(ComicChapterInfo? episode) { final model = VideoModel() ..videoType = 1 ..id = id ..totalEpisode = totalEpisode ..updateStatus = updateStatus ..mediaType = mediaType ..mediaInfo = this ..title = title ..tags = tagDetails ..cover = coverH ..likeCount = countCollect // 视频likecount是收藏数 ..freeTime = freeTime ?? 0 ..collectCount = countCollect ..commentCount = countComment ..playCount = countBrowse ..freeArea = permission == 2 ..coins = price ..originCoins = price ..vidStatus = (VidStatus() ..hasPaid = mediaStatus?.hasPaid == true ..hasCollected = mediaStatus?.hasCollected ..hasLiked = mediaStatus?.hasLiked ..hasDisliked = mediaStatus?.hasDisliked); // 动漫免费集:当前集号在前 freeEpisode 集内 → 播放不拦、进度可全程拖(与 video_logic 免费章节判定同一口径) isCartoonFreeEpisode = isFreeEpisodeNumber(episode?.episodeNumber); final list = episodeList ?? []; if (list.isNotEmpty) { //在列表里找不到当前集就退回第一集,别让播放信息整块空掉 final index = list.indexWhere((e) => e.id == episode?.id); final content = list[index < 0 ? 0 : index].mediaContent; model ..subid = content?.id ..name = content?.name ..sourceURL = content?.videoUrl ..playTime = content?.playTime; } return model; } //acg子集角标显示逻辑,0:会员 1:金币购买 2:免费。为 false(无任何权限)时才展示角标 bool get hasPermission { return (mediaStatus?.hasPaid == true && permission == 1) || permission == 2 || (permission == 0 && globalStore.isVIP) || globalStore.isSuperUp; } CartoonMediaInfo( {this.contents, this.choiceSort, this.countBrowse, this.countCollect, this.countComment, this.countLike, this.countPurchases, this.countPurchasesRate, this.countView, this.createdAt, this.direction, this.freeTime, this.horizontalCover, this.id, this.isDelete, this.kind, this.mId, this.mediaType, this.moduleName, this.permission, this.permissionIconHide, this.price, this.sId, this.discountPrice, this.sectionName, this.sectionSort, this.sortCode, this.status, this.summary, this.tagDetails, this.title, this.totalEpisode, this.currentEpisode, this.updateStatus, this.updateTime, this.updatedAct, this.verticalCover, this.author, this.freeEpisode, this.contentsPrice, this.mediaSubType, this.actress}); CartoonMediaInfo.fromJson(dynamic json) { freeEpisode = json['freeEpisode']; mediaSubType = json['mediaSubType']; author = json['author']; choiceSort = json['choiceSort']; totalEpisode = json['totalEpisode']; currentEpisode = json['currentEpisode']; countBrowse = json['countBrowse']; countCollect = json['countCollect']; countComment = json['countComment']; countLike = json['countLike']; countDisLike = json['countDisLike']; countPurchases = json['countPurchases']; countPurchasesRate = json['countPurchasesRate']; discountPrice = json['discountPrice']; countView = json['countView']; createdAt = json['createdAt']; direction = json['direction']; freeTime = json['freeTime']; horizontalCover = json['horizontalCover']; id = json['id']; isDelete = json['isDelete']; kind = json['kind']; mId = json['mId']; mediaType = json['mediaType']; moduleName = json['moduleName']; permission = json['permission']; permissionIconHide = json['permissionIconHide']; price = json['price']; sId = json['sId']; sectionName = json['sectionName']; sectionSort = json['sectionSort']; sortCode = json['sortCode']; status = json['status']; summary = json['summary']; lsjId = json['lsjId']; actress = json["actress"] == null ? null : ActressInfo.fromJson(json["actress"]); allActress = json["actresses"] == null ? [] : List.from( json["actresses"]!.map((x) => ActressInfo.fromJson(x))); hasFollow = json['hasFollow']; if (json['tagDetails'] != null) { tagDetails = []; json['tagDetails'].forEach((v) { tagDetails?.add(TagsBean.fromMap(v)); }); } if (json['contents'] != null) { contents = []; json['contents'].forEach((v) { contents?.add(MediaContent.fromJson(v)); }); } if (json['title'] == null) { title = json['name']; } else { title = json['title']; } updateStatus = json['updateStatus']; updateTime = json['updateTime']; updatedAct = json['updatedAct']; verticalCover = json['verticalCover']; mediaStatus = VidStatus.fromMap(json['mediaStatus']); contentsPrice = json['contentsPrice']; } ///videoModel是否随机广告 bool isRandomAd() { return randomAdsInfo != null; } Map toJson() { final map = {}; map['mediaSubType'] = mediaSubType; map['choiceSort'] = choiceSort; map['countBrowse'] = countBrowse; map['countCollect'] = countCollect; map['countComment'] = countComment; map['countLike'] = countLike; map['countDisLike'] = countDisLike; map['countPurchases'] = countPurchases; map['countPurchasesRate'] = countPurchasesRate; map['countView'] = countView; map['createdAt'] = createdAt; map['direction'] = direction; map['freeTime'] = freeTime; map['horizontalCover'] = horizontalCover; map['id'] = id; map['isDelete'] = isDelete; map['kind'] = kind; map['mId'] = mId; map['mediaType'] = mediaType; map['moduleName'] = moduleName; map['permission'] = permission; map['permissionIconHide'] = permissionIconHide; map['price'] = price; map['sId'] = sId; map['sectionName'] = sectionName; map['sectionSort'] = sectionSort; map['sortCode'] = sortCode; map['status'] = status; map['summary'] = summary; map['lsjId'] = lsjId; if (tagDetails != null) { map['tagDetails'] = tagDetails?.map((v) => v.toJson()).toList(); } if (contents != null) { map['contents'] = contents?.map((v) => v.toJson()).toList(); } map['hasFollow'] = hasFollow; map['title'] = title; map['totalEpisode'] = totalEpisode; map['currentEpisode'] = currentEpisode; map['updateTime'] = updateTime; map['updateStatus'] = updateStatus; map['updatedAct'] = updatedAct; map['verticalCover'] = verticalCover; map['mediaStatus'] = mediaStatus; return map; } }