// Author: fruit.com.cn import 'json_numbers.dart'; /// 短剧与剧集,字段与后端 `/app/**` 的下发结构对应。 /// /// 封面与播放地址都是后端返回的相对路径(`/files/...`), /// 渲染前用 `AppConfig.mediaUrl` 补上服务地址。 class Episode { const Episode({ required this.id, required this.number, required this.title, required this.isFree, this.videoUrl = '', this.durationSec = 0, this.locked = false, }); final String id; final int number; final String title; final bool isFree; /// 播放地址:详情接口不下发,由 `/app/episodes/{id}/play` 返回后写入。 final String videoUrl; final int durationSec; /// 后端判定需要解锁;解锁能力(金币/VIP)在 M3 接入。 final bool locked; Episode withVideoUrl(String url) => Episode( id: id, number: number, title: title, isFree: isFree, videoUrl: url, durationSec: durationSec, locked: locked, ); factory Episode.fromJson(Map json) { return Episode( id: (json['id'] ?? '').toString(), number: (json['number'] as num?)?.toInt() ?? 0, title: json['title'] as String? ?? '', isFree: json['isFree'] == true, durationSec: (json['durationSec'] as num?)?.toInt() ?? 0, locked: json['locked'] == true, videoUrl: json['playUrl'] as String? ?? '', ); } } class Drama { const Drama({ required this.id, required this.title, required this.synopsis, required this.posterUrl, this.episodes = const [], this.category = '', this.genre = '', this.totalEpisodes = 0, this.freeEpisodes = 0, this.isHot = false, this.viewCount = 0, this.detailLoaded = false, }); final String id; final String title; final String synopsis; final String posterUrl; final List episodes; final String category; final String genre; final int totalEpisodes; final int freeEpisodes; final bool isHot; final int viewCount; /// 详情接口是否已经取过完整剧集列表。 /// 列表卡片只带一集可播预览,需要完整剧集时再进详情。 final bool detailLoaded; /// 卡片只带一集预览,集数以后端给的总集数为准。 int get episodeCount => totalEpisodes > 0 ? totalEpisodes : episodes.length; /// 列表卡片:带一集可播预览(首页直接起播),完整剧集进详情再取。 factory Drama.fromCardJson(Map json) { final preview = json['preview']; return Drama( id: (json['id'] ?? '').toString(), title: json['title'] as String? ?? '', synopsis: json['synopsis'] as String? ?? '', posterUrl: json['cover'] as String? ?? '', episodes: preview is Map ? [Episode.fromJson(preview)] : const [], category: json['category'] as String? ?? '', genre: json['genre'] as String? ?? '', totalEpisodes: (json['totalEpisodes'] as num?)?.toInt() ?? 0, isHot: json['isHot'] == true, // viewCount 后端是 Long,按字符串下发(见 json_numbers.dart) viewCount: jsonToInt(json['viewCount']), ); } /// 详情:带剧集列表。 factory Drama.fromDetailJson(Map json) { return Drama( id: (json['id'] ?? '').toString(), title: json['title'] as String? ?? '', synopsis: json['synopsis'] as String? ?? '', posterUrl: json['cover'] as String? ?? '', category: json['category'] as String? ?? '', genre: json['genre'] as String? ?? '', totalEpisodes: (json['totalEpisodes'] as num?)?.toInt() ?? 0, freeEpisodes: (json['freeEpisodes'] as num?)?.toInt() ?? 0, isHot: json['isHot'] == true, viewCount: jsonToInt(json['viewCount']), detailLoaded: true, episodes: (json['episodes'] as List? ?? const []) .whereType>() .map(Episode.fromJson) .toList(), ); } }