This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
import '../core/network/api_client.dart';
import '../core/network/endpoints.dart';
import '../models/video_item.dart';
import '../models/watch_result.dart';
/// 视频服务:Feed / 详情 / 观看计费上报(《03-接口文档》第 5 章)
class VideoService {
VideoService._();
/// 5.1 Feed 流:type=recommend|latest|hot,游标分页
static Future<FeedPage> fetchFeed({
String type = 'recommend',
String? cursor,
int limit = 10,
int? categoryId,
}) async {
final data = await ApiClient.get<Map<String, dynamic>>(
Endpoints.feed,
queryParameters: {
'type': type,
if (cursor != null && cursor.isNotEmpty) 'cursor': cursor,
'limit': limit,
if (categoryId != null) 'category_id': categoryId,
},
);
final list = ((data['list'] as List?) ?? const [])
.whereType<Map<String, dynamic>>()
.map(VideoItem.fromJson)
.toList();
return FeedPage(items: list, nextCursor: data['next_cursor'] as String?);
}
/// 5.2 视频详情(登录返回续播进度 my_position)
static Future<Map<String, dynamic>> fetchDetail(int videoId) =>
ApiClient.get<Map<String, dynamic>>(Endpoints.videoDetail(videoId));
/// 5.3 观看上报(计费):开始播放时调用,客户端已按 video_id+日期 本地去重
static Future<WatchResult> reportWatch(int videoId, {int? position}) async {
final data = await ApiClient.post<Map<String, dynamic>>(
Endpoints.videoWatch(videoId),
data: {if (position != null) 'position': position},
);
return WatchResult.fromJson(data);
}
/// 5.4 完整观看上报:进度 ≥95% 或自然播完时调用(创作者收益计数)
/// 返回 credited:今日首次有效为 true,其余情况 false(无需 UI 处理)
static Future<bool> reportComplete(int videoId, int watchDuration) async {
final data = await ApiClient.post<Map<String, dynamic>>(
Endpoints.videoWatchComplete(videoId),
data: {'watch_duration': watchDuration},
);
return (data['credited'] as bool?) ?? false;
}
/// 5.29 点赞/取消
static Future<Map<String, dynamic>> toggleLike(int videoId) =>
ApiClient.post<Map<String, dynamic>>(Endpoints.videoLike(videoId));
/// 5.30 收藏/取消
static Future<Map<String, dynamic>> toggleFavorite(int videoId) =>
ApiClient.post<Map<String, dynamic>>(Endpoints.videoFavorite(videoId));
/// 5.12 编辑视频(仅作者):仅传需要修改的字段
static Future<void> updateVideo(
int videoId, {
String? title,
String? description,
int? categoryId,
List<String>? tags,
}) async {
await ApiClient.put<Map<String, dynamic>>(
Endpoints.videoEdit(videoId),
data: {
if (title != null) 'title': title,
if (description != null) 'description': description,
if (categoryId != null) 'category_id': categoryId,
if (tags != null) 'tags': tags,
},
);
}
/// 5.13 删除视频(仅作者,服务端软删除)
static Future<void> deleteVideo(int videoId) =>
ApiClient.delete<Map<String, dynamic>>(Endpoints.videoDelete(videoId));
/// 5.9 标签视频列表(游客可):sort=hot(默认)| latest,普通分页
static Future<TagVideosPage> fetchTagVideos(
String tagName, {
String sort = 'hot',
int page = 1,
int pageSize = 20,
}) async {
final data = await ApiClient.get<Map<String, dynamic>>(
Endpoints.tagVideos(Uri.encodeComponent(tagName)),
queryParameters: {'sort': sort, 'page': page, 'page_size': pageSize},
);
return TagVideosPage.fromJson(data, fallbackTag: tagName);
}
}
/// Feed 分页结果(游标)
class FeedPage {
FeedPage({required this.items, this.nextCursor});
final List<VideoItem> items;
/// null 表示没有更多
final String? nextCursor;
}
/// 标签视频分页结果(普通 page/page_size 分页)
class TagVideosPage {
TagVideosPage({required this.tag, required this.total, required this.items});
final String tag;
final int total;
final List<VideoItem> items;
factory TagVideosPage.fromJson(
Map<String, dynamic> json, {
String fallbackTag = '',
}) =>
TagVideosPage(
tag: (json['tag'] as String?) ?? fallbackTag,
total: (json['total'] as num?)?.toInt() ?? 0,
items: ((json['list'] as List?) ?? const [])
.whereType<Map<String, dynamic>>()
.map(VideoItem.fromJson)
.toList(),
);
}