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 fetchFeed({ String type = 'recommend', String? cursor, int limit = 10, int? categoryId, }) async { final data = await ApiClient.get>( 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(VideoItem.fromJson) .toList(); return FeedPage(items: list, nextCursor: data['next_cursor'] as String?); } /// 5.2 视频详情(登录返回续播进度 my_position) static Future> fetchDetail(int videoId) => ApiClient.get>(Endpoints.videoDetail(videoId)); /// 当前视频所属系列 + 全部集数(播放器抽屉用) static Future> fetchVideoSeries(int videoId) async { try { return await ApiClient.get>(Endpoints.videoSeries(videoId)); } catch (_) { return {'has_series': false}; } } /// 5.3 观看上报(计费):开始播放时调用,客户端已按 video_id+日期 本地去重 static Future reportWatch(int videoId, {int? position}) async { final data = await ApiClient.post>( Endpoints.videoWatch(videoId), data: {if (position != null) 'position': position}, ); return WatchResult.fromJson(data); } /// 5.4 完整观看上报:进度 ≥95% 或自然播完时调用(创作者收益计数) /// 返回 credited:今日首次有效为 true,其余情况 false(无需 UI 处理) static Future reportComplete(int videoId, int watchDuration) async { final data = await ApiClient.post>( Endpoints.videoWatchComplete(videoId), data: {'watch_duration': watchDuration}, ); return (data['credited'] as bool?) ?? false; } /// 5.29 点赞/取消 static Future> toggleLike(int videoId) => ApiClient.post>(Endpoints.videoLike(videoId)); /// 5.30 收藏/取消 static Future> toggleFavorite(int videoId) => ApiClient.post>(Endpoints.videoFavorite(videoId)); /// 5.12 编辑视频(仅作者):仅传需要修改的字段 static Future updateVideo( int videoId, { String? title, String? description, int? categoryId, List? tags, }) async { await ApiClient.put>( 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 deleteVideo(int videoId) => ApiClient.delete>(Endpoints.videoDelete(videoId)); /// 5.9 标签视频列表(游客可):sort=hot(默认)| latest,普通分页 static Future fetchTagVideos( String tagName, { String sort = 'hot', int page = 1, int pageSize = 20, }) async { final data = await ApiClient.get>( 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 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 items; factory TagVideosPage.fromJson( Map 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(VideoItem.fromJson) .toList(), ); }