Files
ReelShort/lib/core/data/models/drama.dart
T

175 lines
5.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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,
this.likeCount,
this.commentCount,
this.thumbnailsUrl,
});
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;
final int? likeCount;
final int? commentCount;
final String? thumbnailsUrl;
Episode withVideoUrl(
String url, {
int? likeCount,
int? commentCount,
String? thumbnailsUrl,
}) => Episode(
id: id,
number: number,
title: title,
isFree: isFree,
videoUrl: url,
durationSec: durationSec,
locked: locked,
likeCount: likeCount ?? this.likeCount,
commentCount: commentCount ?? this.commentCount,
thumbnailsUrl: thumbnailsUrl ?? this.thumbnailsUrl,
);
factory Episode.fromJson(Map<String, dynamic> 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? ?? '',
likeCount: _optionalInt(json['likeCount']),
commentCount: _optionalInt(json['commentCount']),
thumbnailsUrl: _optionalString(json['thumbnailsUrl']),
);
}
}
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.likeCount,
this.favoriteCount,
this.commentCount,
this.language = '',
this.detailLoaded = false,
});
final String id;
final String title;
final String synopsis;
final String posterUrl;
final List<Episode> episodes;
final String category;
final String genre;
final int totalEpisodes;
final int freeEpisodes;
final bool isHot;
final int viewCount;
/// 详情下发的互动数量;卡片或旧接口没有这些字段时为 null(客户端不显示数字)。
final int? likeCount;
final int? favoriteCount;
final int? commentCount;
/// 卡片语种(如 zh-CN / id),用于剧场语言筛选。
final String language;
/// 详情接口是否已经取过完整剧集列表。
/// 列表卡片只带一集可播预览,需要完整剧集时再进详情。
final bool detailLoaded;
/// 卡片只带一集预览,集数以后端给的总集数为准。
int get episodeCount => totalEpisodes > 0 ? totalEpisodes : episodes.length;
/// 列表卡片:带一集可播预览(首页直接起播),完整剧集进详情再取。
factory Drama.fromCardJson(Map<String, dynamic> 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<String, dynamic>
? [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']),
likeCount: _optionalInt(json['likeCount']),
favoriteCount: _optionalInt(json['favoriteCount']),
commentCount: _optionalInt(json['commentCount']),
language: json['language'] as String? ?? '',
);
}
/// 详情:带剧集列表。
factory Drama.fromDetailJson(Map<String, dynamic> 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']),
likeCount: _optionalInt(json['likeCount']),
favoriteCount: _optionalInt(json['favoriteCount']),
commentCount: _optionalInt(json['commentCount']),
language: json['language'] as String? ?? '',
detailLoaded: true,
episodes: (json['episodes'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.map(Episode.fromJson)
.toList(),
);
}
}
int? _optionalInt(dynamic value) => value == null ? null : jsonToInt(value);
String? _optionalString(dynamic value) {
final text = value is String ? value.trim() : '';
return text.isEmpty ? null : text;
}