feat: show episode resource

This commit is contained in:
Simon Ding
2024-07-27 22:22:06 +08:00
parent feecc9f983
commit eae35ce862
21 changed files with 274 additions and 399 deletions

View File

@@ -172,7 +172,7 @@ class _NestedTabBarState extends ConsumerState<NestedTabBar>
@override
Widget build(BuildContext context) {
var torrents = ref.watch(movieTorrentsDataProvider(widget.id));
var torrents = ref.watch(mediaTorrentsDataProvider(TorrentQuery(mediaId: widget.id)));
var histories = ref.watch(mediaHistoryDataProvider(widget.id));
return Column(
@@ -250,7 +250,7 @@ class _NestedTabBarState extends ConsumerState<NestedTabBar>
icon: const Icon(Icons.download),
onPressed: () {
ref
.read(movieTorrentsDataProvider(widget.id)
.read(mediaTorrentsDataProvider(TorrentQuery(mediaId: widget.id))
.notifier)
.download(torrent)
.then((v) =>

View File

@@ -13,7 +13,8 @@ class APIs {
static final settingsGeneralUrl = "$_baseUrl/api/v1/setting/general";
static final watchlistTvUrl = "$_baseUrl/api/v1/media/tv/watchlist";
static final watchlistMovieUrl = "$_baseUrl/api/v1/media/movie/watchlist";
static final availableMoviesUrl = "$_baseUrl/api/v1/media/movie/resources/";
static final availableTorrentsUrl = "$_baseUrl/api/v1/media/torrents/";
static final downloadTorrentUrl = "$_baseUrl/api/v1/media/torrents/download";
static final seriesDetailUrl = "$_baseUrl/api/v1/media/record/";
static final suggestedTvName = "$_baseUrl/api/v1/media/suggest/";
static final searchAndDownloadUrl = "$_baseUrl/api/v1/indexer/download";

View File

@@ -97,7 +97,7 @@ class SeriesDetails {
class Episodes {
int? id;
int? seriesId;
int? mediaId;
int? episodeNumber;
String? title;
String? airDate;
@@ -107,7 +107,7 @@ class Episodes {
Episodes(
{this.id,
this.seriesId,
this.mediaId,
this.episodeNumber,
this.title,
this.airDate,
@@ -117,7 +117,7 @@ class Episodes {
Episodes.fromJson(Map<String, dynamic> json) {
id = json['id'];
seriesId = json['series_id'];
mediaId = json['media_id'];
episodeNumber = json['episode_number'];
title = json['title'];
airDate = json['air_date'];
@@ -126,3 +126,75 @@ class Episodes {
overview = json['overview'];
}
}
var mediaTorrentsDataProvider = AsyncNotifierProvider.autoDispose
.family<MediaTorrentResource, List<TorrentResource>, TorrentQuery>(
MediaTorrentResource.new);
class TorrentQuery {
final String mediaId;
final int seasonNumber;
final int episodeNumber;
TorrentQuery(
{required this.mediaId, this.seasonNumber = 0, this.episodeNumber = 0});
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data["id"] = int.parse(mediaId);
data["season"] = seasonNumber;
data["episode"] = episodeNumber;
return data;
}
}
class MediaTorrentResource extends AutoDisposeFamilyAsyncNotifier<
List<TorrentResource>, TorrentQuery> {
TorrentQuery? query;
@override
FutureOr<List<TorrentResource>> build(TorrentQuery arg) async {
query = arg;
final dio = await APIs.getDio();
var resp = await dio.post(APIs.availableTorrentsUrl, data: arg.toJson());
var rsp = ServerResponse.fromJson(resp.data);
if (rsp.code != 0) {
throw rsp.message;
}
return (rsp.data as List).map((v) => TorrentResource.fromJson(v)).toList();
}
Future<void> download(TorrentResource res) async {
final data = res.toJson();
data.addAll(query!.toJson());
final dio = await APIs.getDio();
var resp = await dio.post(APIs.downloadTorrentUrl, data: data);
var rsp = ServerResponse.fromJson(resp.data);
if (rsp.code != 0) {
throw rsp.message;
}
}
}
class TorrentResource {
TorrentResource({this.name, this.size, this.seeders, this.peers, this.link});
String? name;
int? size;
int? seeders;
int? peers;
String? link;
factory TorrentResource.fromJson(Map<String, dynamic> json) {
return TorrentResource(
name: json["name"],
size: json["size"],
seeders: json["seeders"],
peers: json["peers"],
link: json["link"]);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['name'] = name;
data['size'] = size;
data["link"] = link;
return data;
}
}

View File

@@ -44,10 +44,6 @@ final movieWatchlistDataProvider = FutureProvider.autoDispose((ref) async {
var searchPageDataProvider = AsyncNotifierProvider.autoDispose
.family<SearchPageData, List<SearchResult>, String>(SearchPageData.new);
var movieTorrentsDataProvider = AsyncNotifierProvider.autoDispose
.family<MovieTorrentResource, List<TorrentResource>, String>(
MovieTorrentResource.new);
class SearchPageData
extends AutoDisposeFamilyAsyncNotifier<List<SearchResult>, String> {
List<SearchResult> list = List.empty(growable: true);
@@ -245,56 +241,3 @@ class SearchResult {
}
}
class MovieTorrentResource
extends AutoDisposeFamilyAsyncNotifier<List<TorrentResource>, String> {
String? mediaId;
@override
FutureOr<List<TorrentResource>> build(String id) async {
mediaId = id;
final dio = await APIs.getDio();
var resp = await dio.get(APIs.availableMoviesUrl + id);
var rsp = ServerResponse.fromJson(resp.data);
if (rsp.code != 0) {
throw rsp.message;
}
return (rsp.data as List).map((v) => TorrentResource.fromJson(v)).toList();
}
Future<void> download(TorrentResource res) async {
var m = res.toJson();
m["media_id"] = int.parse(mediaId!);
final dio = await APIs.getDio();
var resp = await dio.post(APIs.availableMoviesUrl, data: m);
var rsp = ServerResponse.fromJson(resp.data);
if (rsp.code != 0) {
throw rsp.message;
}
}
}
class TorrentResource {
TorrentResource({this.name, this.size, this.seeders, this.peers, this.link});
String? name;
int? size;
int? seeders;
int? peers;
String? link;
factory TorrentResource.fromJson(Map<String, dynamic> json) {
return TorrentResource(
name: json["name"],
size: json["size"],
seeders: json["seeders"],
peers: json["peers"],
link: json["link"]);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['name'] = name;
data['size'] = size;
data["link"] = link;
return data;
}
}

View File

@@ -591,8 +591,8 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: Container(
constraints: const BoxConstraints(maxWidth: 200),
child: SizedBox(
width: 300,
child: body,
),
),

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@@ -26,7 +28,6 @@ class TvDetailsPage extends ConsumerStatefulWidget {
}
class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
@override
void initState() {
super.initState();
@@ -45,7 +46,7 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
DataCell(Text("${ep.title}")),
DataCell(Opacity(
opacity: 0.5,
child: Text(ep.airDate??"-"),
child: Text(ep.airDate ?? "-"),
)),
DataCell(
Opacity(
@@ -86,7 +87,9 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
width: 10,
),
IconButton(
onPressed: () {}, icon: const Icon(Icons.manage_search))
onPressed: () => showAvailableTorrents(widget.seriesId,
ep.seasonNumber ?? 0, ep.episodeNumber ?? 0),
icon: const Icon(Icons.manage_search))
],
))
]);
@@ -198,7 +201,7 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
),
const Text(""),
Text(
details.overview??"",
details.overview ?? "",
),
],
)),
@@ -239,4 +242,86 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
},
loading: () => const MyProgressIndicator());
}
Future<void> showAvailableTorrents(String id, int season, int episode) {
final torrents = ref.watch(mediaTorrentsDataProvider(TorrentQuery(
mediaId: id, seasonNumber: season, episodeNumber: episode))
.future);
return showDialog<void>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
title: Text("资源"),
content: FutureBuilder(
future: torrents,
builder: (context, snapshot) {
return SelectionArea(
child: Container(
constraints:
BoxConstraints(maxHeight: 400, maxWidth: 1000),
child: () {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.hasError) {
// 请求失败,显示错误
return Text("Error: ${snapshot.error}");
} else {
// 请求成功,显示数据
final v = snapshot.data;
return SingleChildScrollView(
child: DataTable(
dataTextStyle:
TextStyle(fontSize: 14, height: 0),
columns: const [
DataColumn(label: Text("名称")),
DataColumn(label: Text("大小")),
DataColumn(label: Text("seeders")),
DataColumn(label: Text("peers")),
DataColumn(label: Text("操作"))
],
rows: List.generate(v!.length, (i) {
final torrent = v[i];
return DataRow(cells: [
DataCell(Text("${torrent.name}")),
DataCell(Text(
"${torrent.size?.readableFileSize()}")),
DataCell(
Text("${torrent.seeders}")),
DataCell(Text("${torrent.peers}")),
DataCell(IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
await ref
.read(mediaTorrentsDataProvider(
TorrentQuery(
mediaId: id,
seasonNumber:
season,
episodeNumber:
episode))
.notifier)
.download(torrent)
.then((v) {
Navigator.of(context).pop();
Utils.showSnakeBar(
"开始下载:${torrent.name}");
}).onError((error, trace) =>
Utils.showSnakeBar(
"下载失败:$error"));
},
))
]);
})));
}
} else {
// 请求未结束显示loading
return MyProgressIndicator();
}
}()));
}));
},
);
}
}