为什么:客户端原型在 14d7bd3 之后又做了一轮(页面分层、发现页、分类滑动、播放器控件等),
一直留在工作区没入库。下一批账号链路要改其中的 app.dart / home_view / player_view / mine_view,
先归档这批既存改动,本批的实际 diff 才说得清边界。
只归档,不改行为:当前工作区 flutter analyze 0 问题、flutter test 148 全过。
126 lines
3.8 KiB
Dart
126 lines
3.8 KiB
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<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? ?? '',
|
|
);
|
|
}
|
|
}
|
|
|
|
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<Episode> 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<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: (json['viewCount'] as num?)?.toInt() ?? 0,
|
|
);
|
|
}
|
|
|
|
/// 详情:带剧集列表。
|
|
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: (json['viewCount'] as num?)?.toInt() ?? 0,
|
|
detailLoaded: true,
|
|
episodes: (json['episodes'] as List? ?? const [])
|
|
.whereType<Map<String, dynamic>>()
|
|
.map(Episode.fromJson)
|
|
.toList(),
|
|
);
|
|
}
|
|
}
|