mirror of
https://github.com/simon-ding/polaris.git
synced 2026-06-08 02:57:38 +08:00
feat: show episode resource
This commit is contained in:
@@ -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) =>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}()));
|
||||
}));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user