192 lines
6.1 KiB
Dart
192 lines
6.1 KiB
Dart
import 'package:hgdj/hj_model/drama_media_info.dart';
|
||
import 'package:hgdj/hj_model/media_content.dart';
|
||
|
||
/// 一条短剧观看记录:续播位置 + 列表展示用的剧信息快照。本地记录,不走后端,见 [DramaResumeStore]。
|
||
/// 落库时整条存成 model_data,所以 [mediaId] 必须写进 json——
|
||
/// 基类只回 model_data 一列,读盘时全靠它认领是哪部剧
|
||
class DramaResume {
|
||
final String mediaId; //哪部剧,同时是行主键
|
||
final String contentId; //上次看的分集。定位只认它,别再存「第几集」——分页深度靠边翻边找就够了
|
||
final int progressSeconds; //看到第几秒
|
||
|
||
/// 剧信息快照(封面/剧名/集数)。续播定位用不到,是历史记录页拿它画卡片——
|
||
/// 只存 id 的话列表除了 id 什么都渲染不出来
|
||
final DramaMediaInfo? drama;
|
||
|
||
/// 本条写入时刻(毫秒)。历史记录页按它倒序,不看 db 的 create_time——
|
||
/// 老数据没这个字段,读出来是 0,排在最后
|
||
final int watchedAt;
|
||
|
||
const DramaResume({
|
||
required this.mediaId,
|
||
required this.contentId,
|
||
required this.progressSeconds,
|
||
this.drama,
|
||
this.watchedAt = 0,
|
||
});
|
||
|
||
DramaResume.fromJson(dynamic json)
|
||
: mediaId = json?['mediaId'] ?? '',
|
||
contentId = json?['contentId'] ?? '',
|
||
progressSeconds = json?['progressSeconds'] ?? 0,
|
||
drama = json?['drama'] == null
|
||
? null
|
||
: DramaMediaInfo.fromJson(json['drama']),
|
||
watchedAt = json?['watchedAt'] ?? 0;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'mediaId': mediaId,
|
||
'contentId': contentId,
|
||
'progressSeconds': progressSeconds,
|
||
'drama': drama?.toJson(),
|
||
'watchedAt': watchedAt,
|
||
};
|
||
}
|
||
|
||
/// 付费墙:文案/价格/余额全部服务端下发,前端不自己拼
|
||
class DramaPaywall {
|
||
String? checkoutContextId; //本次付费墙上下文,充值/开卡归因和下单都要带
|
||
String? title;
|
||
int? coinBalance;
|
||
int? unlockCoin;
|
||
String? coinButtonText;
|
||
String? cardButtonText;
|
||
|
||
DramaPaywall.fromJson(dynamic json) {
|
||
json ??= {};
|
||
checkoutContextId = json['checkoutContextId'];
|
||
title = json['title'];
|
||
coinBalance = json['coinBalance'];
|
||
unlockCoin = json['unlockCoin'];
|
||
coinButtonText = json['coinButtonText'];
|
||
cardButtonText = json['cardButtonText'];
|
||
}
|
||
|
||
String get titleUI => title?.isNotEmpty == true ? title! : '更多精彩解锁即享';
|
||
|
||
String get coinButtonUI => coinButtonText?.isNotEmpty == true
|
||
? coinButtonText!
|
||
: '${unlockCoin ?? 0}金币解锁';
|
||
|
||
String get cardButtonUI =>
|
||
cardButtonText?.isNotEmpty == true ? cardButtonText! : '开通短剧卡免费看';
|
||
}
|
||
|
||
/// AI短剧 Feed 的一条:某部剧 + 该剧第一集。整页走 ListBaseModel<DramaFeedItem>
|
||
class DramaFeedItem {
|
||
DramaMediaInfo? media;
|
||
MediaContent? content;
|
||
|
||
DramaFeedItem.fromJson(dynamic json) {
|
||
json ??= {};
|
||
//缺 media 就不造空模型,上层按 null 过滤掉这条,别让信息流多出一个没剧名没地址的空位
|
||
if (json['media'] != null) media = DramaMediaInfo.fromJson(json['media']);
|
||
if (json['content'] != null)
|
||
content = MediaContent.fromJson(json['content']);
|
||
}
|
||
}
|
||
|
||
/// 短剧专题(热门短剧 Tab)
|
||
class DramaTopic {
|
||
String? topicId;
|
||
String? name;
|
||
String? topicType;
|
||
String? systemKey;
|
||
int? sort;
|
||
int? workCount;
|
||
|
||
DramaTopic({
|
||
this.topicId,
|
||
this.name,
|
||
this.topicType,
|
||
this.systemKey,
|
||
this.sort,
|
||
this.workCount,
|
||
});
|
||
|
||
DramaTopic.fromJson(dynamic json) {
|
||
json ??= {};
|
||
topicId = json['topicId']?.toString();
|
||
name = json['name']?.toString();
|
||
topicType = json['topicType']?.toString();
|
||
systemKey = json['systemKey']?.toString();
|
||
sort = json['sort'] is int
|
||
? json['sort'] as int
|
||
: int.tryParse('${json['sort'] ?? ''}');
|
||
workCount = json['workCount'] is int
|
||
? json['workCount'] as int
|
||
: int.tryParse('${json['workCount'] ?? ''}');
|
||
}
|
||
}
|
||
|
||
/// 短剧搜索结果(`/media/search` kind=4)
|
||
class DramaSearchResult {
|
||
List<DramaMediaInfo> list = [];
|
||
List<DramaMediaInfo> tagMediaList = [];
|
||
String? tagID;
|
||
bool hasNext = false;
|
||
|
||
DramaSearchResult.fromJson(dynamic json) {
|
||
json ??= {};
|
||
hasNext = json['hasNext'] == true;
|
||
tagID = json['tagID']?.toString();
|
||
list = (json['list'] as List?)
|
||
?.map((e) => DramaMediaInfo.fromJson(e))
|
||
.toList() ??
|
||
[];
|
||
tagMediaList = (json['tagMediaList'] as List?)
|
||
?.map((e) => DramaMediaInfo.fromJson(e))
|
||
.toList() ??
|
||
[];
|
||
}
|
||
}
|
||
|
||
/// 单集金币解锁的下单结果
|
||
class DramaUnlockResult {
|
||
String? orderId;
|
||
String? mediaId;
|
||
String? contentId;
|
||
int? paidCoin;
|
||
int? coinBalance;
|
||
|
||
DramaUnlockResult.fromJson(Map<String, dynamic>? json) {
|
||
json ??= {};
|
||
orderId = json['orderId'];
|
||
mediaId = json['mediaId'];
|
||
contentId = json['contentId'];
|
||
paidCoin = json['paidCoin'];
|
||
coinBalance = json['coinBalance'];
|
||
}
|
||
}
|
||
|
||
/// 短剧单集下载授权结果。权益/上下架/次数校验和扣次都在服务端一次做完,
|
||
/// 客户端只负责拿地址开下载,不再自己算次数
|
||
class DramaDownloadAuth {
|
||
String? mediaId;
|
||
String? contentId;
|
||
int? episodeNumber;
|
||
String? name;
|
||
String? cover;
|
||
String? downloadUrl; // H.264 m3u8,可能是相对地址
|
||
String? h265DownloadUrl; // H.265 m3u8,没有时为空串
|
||
int? mediaSize; // 字节
|
||
String? expiresAt; // 本次地址失效时间(签发后 6 小时)
|
||
int? remainingDownloadCount; // 扣完之后钱包剩余下载次数,直接用它顶掉本地值
|
||
bool? charged; // false = 幂等重试命中,没有重复扣次
|
||
|
||
DramaDownloadAuth.fromJson(Map<String, dynamic>? json) {
|
||
json ??= {};
|
||
mediaId = json['mediaId'];
|
||
contentId = json['contentId'];
|
||
episodeNumber = json['episodeNumber'];
|
||
name = json['name'];
|
||
cover = json['cover'];
|
||
downloadUrl = json['downloadUrl'];
|
||
h265DownloadUrl = json['h265DownloadUrl'];
|
||
mediaSize = json['mediaSize'];
|
||
expiresAt = json['expiresAt'];
|
||
remainingDownloadCount = json['remainingDownloadCount'];
|
||
charged = json['charged'] == true;
|
||
}
|
||
}
|