feat(flutter): 接入分类记录收藏与下载授权

This commit is contained in:
Zhou Weidong committed 2026-10-17 14:49:16 +08:00
1 parent 00280ca0da
commit cc4d7c5a9d
52 files changed
+4440 -171

No files matched your search

+15
View File
@@ -79,6 +79,21 @@ class ApiClient {
);
}
Future<Object?> put(
String path, {
Object? body,
Map<String, String>? query,
bool skipAuthRetry = false,
}) {
return _request(
'PUT',
path,
body: body,
query: query,
skipAuthRetry: skipAuthRetry,
);
}
Future<Object?> delete(
String path, {
Object? body,
+21
View File
@@ -0,0 +1,21 @@
// Author: fruit.com.cn
import '../models/content_category.dart';
import 'api_client.dart';
/// 内容分类接口,对应契约 `GET /app/categories`。
class CategoryApi {
const CategoryApi(this._client);
final ApiClient _client;
/// 拉取已启用分类,id 全程按字符串解析。
Future<List<ContentCategory>> list() async {
final data = await _client.get('/app/categories');
if (data is! List) return const [];
return [
for (final item in data)
if (item is Map<String, dynamic>) ContentCategory.fromJson(item),
];
}
}
+101
View File
@@ -0,0 +1,101 @@
// Author: fruit.com.cn
import '../models/download_license.dart';
import '../models/download_policy.dart';
import 'api_client.dart';
/// 离线下载授权接口,对应契约 §1.6 的全部 8 个接口。
///
/// 设备号由 [ApiClient] 统一通过 `X-Device-Id` 发送,本类不另造设备参数。
class DownloadApi {
const DownloadApi(this._client);
final ApiClient _client;
/// 拉取配额和偏好策略。
Future<DownloadPolicy> policy() async {
return DownloadPolicy.fromJson(
_asMap(await _client.get('/app/downloads/policy')),
);
}
/// 更新仅 Wi-Fi 与最高清晰度偏好。
Future<void> updateSettings({bool? wifiOnly, String? maxQuality}) async {
await _client.put(
'/app/downloads/settings',
body: {'wifiOnly': ?wifiOnly, 'maxQuality': ?maxQuality},
);
}
/// 申请下载授权;同集重复申请由服务端保证幂等。
Future<DownloadLicense> grant(String episodeId, {String? quality}) async {
return DownloadLicense.fromJson(
_asMap(
await _client.post(
'/app/downloads',
body: {'episodeId': episodeId, 'quality': ?quality},
),
),
);
}
/// 拉取当前设备的授权列表。
Future<List<DownloadLicense>> list() async {
final data = await _client.get('/app/downloads');
if (data is! List) return const [];
return [
for (final item in data)
if (item is Map<String, dynamic>) DownloadLicense.fromJson(item),
];
}
/// 续传前刷新短时效下载地址。
Future<DownloadLicense> refreshUrl(String episodeId) async {
return DownloadLicense.fromJson(
_asMap(await _client.get('/app/downloads/$episodeId/url')),
);
}
/// 上报下载进度;本批只提供方法,不启动文件落盘。
Future<DownloadLicense> reportProgress(
String episodeId, {
String? deviceId,
String? status,
int? progressPercent,
int? downloadedBytes,
int? totalBytes,
String? errorMsg,
}) async {
return DownloadLicense.fromJson(
_asMap(
await _client.post(
'/app/downloads/$episodeId/progress',
body: {
'deviceId': ?deviceId,
'status': ?status,
'progressPercent': ?progressPercent,
'downloadedBytes': ?downloadedBytes,
'totalBytes': ?totalBytes,
'errorMsg': ?errorMsg,
},
),
),
);
}
/// 吊销单集授权。
Future<void> remove(String episodeId) async {
await _client.delete('/app/downloads/$episodeId');
}
/// 清空当前设备授权并返回清除条数。
Future<int> clear() async {
final data = await _client.delete('/app/downloads');
return data is int ? data : 0;
}
Map<String, dynamic> _asMap(Object? data) {
if (data is Map<String, dynamic>) return data;
throw const ApiException(-1, '');
}
}
+5
View File
@@ -56,4 +56,9 @@ class WatchApi {
Future<void> removeProgress(String dramaId) async {
await _client.delete('/app/watch/progress/$dramaId');
}
/// 清空当前账号的全部观看记录。
Future<void> clearProgress() async {
await _client.delete('/app/watch/progress');
}
}