mirror of
https://github.com/simon-ding/polaris.git
synced 2026-06-09 19:47:47 +08:00
separate api calls
This commit is contained in:
@@ -88,7 +88,7 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
return ProviderScope(
|
return ProviderScope(
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
title: 'Flutter Demo',
|
title: 'Polaris',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
// This is the theme of your application.
|
// This is the theme of your application.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,16 +1,40 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
import 'package:ui/server_response.dart';
|
import 'package:ui/server_response.dart';
|
||||||
|
|
||||||
var seriesDetailsProvider = FutureProvider.family((ref, seriesId) async {
|
var seriesDetailsProvider = AsyncNotifierProvider.autoDispose
|
||||||
var resp = await Dio().get("${APIs.seriesDetailUrl}$seriesId");
|
.family<SeriesDetailData, SeriesDetails, String>(SeriesDetailData.new);
|
||||||
|
|
||||||
|
class SeriesDetailData
|
||||||
|
extends AutoDisposeFamilyAsyncNotifier<SeriesDetails, String> {
|
||||||
|
@override
|
||||||
|
FutureOr<SeriesDetails> build(String arg) async {
|
||||||
|
var resp = await Dio().get("${APIs.seriesDetailUrl}$arg");
|
||||||
var rsp = ServerResponse.fromJson(resp.data);
|
var rsp = ServerResponse.fromJson(resp.data);
|
||||||
if (rsp.code != 0) {
|
if (rsp.code != 0) {
|
||||||
throw rsp.message;
|
throw rsp.message;
|
||||||
}
|
}
|
||||||
return SeriesDetails.fromJson(rsp.data);
|
return SeriesDetails.fromJson(rsp.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> searchAndDownload(
|
||||||
|
String seriesId, int seasonNum, int episodeNum) async {
|
||||||
|
var resp = await Dio().post(APIs.searchAndDownloadUrl, data: {
|
||||||
|
"id": int.parse(seriesId),
|
||||||
|
"season": seasonNum,
|
||||||
|
"episode": episodeNum,
|
||||||
});
|
});
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
var name = (sp.data as Map<String, dynamic>)["name"];
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SeriesDetails {
|
class SeriesDetails {
|
||||||
int? id;
|
int? id;
|
||||||
|
|||||||
191
ui/lib/providers/settings.dart
Normal file
191
ui/lib/providers/settings.dart
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:quiver/strings.dart';
|
||||||
|
import 'package:ui/providers/APIs.dart';
|
||||||
|
import 'package:ui/server_response.dart';
|
||||||
|
|
||||||
|
var tmdbApiSettingProvider =
|
||||||
|
AsyncNotifierProvider<TmdbApiSetting, String>(TmdbApiSetting.new);
|
||||||
|
|
||||||
|
var indexersProvider =
|
||||||
|
AsyncNotifierProvider<IndexerSetting, List<Indexer>>(IndexerSetting.new);
|
||||||
|
|
||||||
|
var dwonloadClientsProvider =
|
||||||
|
AsyncNotifierProvider<DownloadClientSetting, List<DownloadClient>>(
|
||||||
|
DownloadClientSetting.new);
|
||||||
|
|
||||||
|
class TmdbApiSetting extends AsyncNotifier<String> {
|
||||||
|
@override
|
||||||
|
FutureOr<String> build() async {
|
||||||
|
final dio = Dio();
|
||||||
|
var resp = await dio
|
||||||
|
.get(APIs.settingsUrl, queryParameters: {"key": APIs.tmdbApiKey});
|
||||||
|
var rrr = ServerResponse.fromJson(resp.data);
|
||||||
|
if (rrr.code != 0) {
|
||||||
|
throw rrr.message;
|
||||||
|
}
|
||||||
|
var data = rrr.data as Map<String, dynamic>;
|
||||||
|
var key = data[APIs.tmdbApiKey] as String;
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> submitSettings(String v) async {
|
||||||
|
var resp = await Dio().post(APIs.settingsUrl, data: {APIs.tmdbApiKey: v});
|
||||||
|
var sp = ServerResponse.fromJson(resp.data as Map<String, dynamic>);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IndexerSetting extends AsyncNotifier<List<Indexer>> {
|
||||||
|
final dio = Dio();
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<Indexer>> build() async {
|
||||||
|
var resp = await dio.get(APIs.allIndexersUrl);
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
List<Indexer> indexers = List.empty(growable: true);
|
||||||
|
for (final item in sp.data as List) {
|
||||||
|
indexers.add(Indexer.fromJson(item));
|
||||||
|
}
|
||||||
|
return indexers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addIndexer(Indexer indexer) async {
|
||||||
|
if (isBlank(indexer.name) ||
|
||||||
|
isBlank(indexer.url) ||
|
||||||
|
isBlank(indexer.apiKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var resp = await dio.post(APIs.addIndexerUrl, data: indexer.toJson());
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
ref.invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteIndexer(int id) async {
|
||||||
|
var resp = await dio.delete("${APIs.delIndexerUrl}$id");
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
ref.invalidateSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Indexer {
|
||||||
|
String? name;
|
||||||
|
String? url;
|
||||||
|
String? apiKey;
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
Indexer({this.name, this.url, this.apiKey});
|
||||||
|
|
||||||
|
Indexer.fromJson(Map<String, dynamic> json) {
|
||||||
|
name = json['name'];
|
||||||
|
url = json['url'];
|
||||||
|
apiKey = json['api_key'];
|
||||||
|
id = json["id"];
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['name'] = this.name;
|
||||||
|
data['url'] = this.url;
|
||||||
|
data['api_key'] = this.apiKey;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadClientSetting extends AsyncNotifier<List<DownloadClient>> {
|
||||||
|
final dio = Dio();
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<DownloadClient>> build() async {
|
||||||
|
var resp = await dio.get(APIs.allDownloadClientsUrl);
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
List<DownloadClient> indexers = List.empty(growable: true);
|
||||||
|
for (final item in sp.data as List) {
|
||||||
|
indexers.add(DownloadClient.fromJson(item));
|
||||||
|
}
|
||||||
|
return indexers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addDownloadClients(String name, String url) async {
|
||||||
|
if (name.isEmpty || url.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dio = Dio();
|
||||||
|
var resp = await dio.post(APIs.addDownloadClientUrl, data: {
|
||||||
|
"name": name,
|
||||||
|
"url": url,
|
||||||
|
});
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
ref.invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteDownloadClients(int id) async {
|
||||||
|
var dio = Dio();
|
||||||
|
var resp = await dio.delete("${APIs.delDownloadClientUrl}$id");
|
||||||
|
var sp = ServerResponse.fromJson(resp.data);
|
||||||
|
if (sp.code != 0) {
|
||||||
|
throw sp.message;
|
||||||
|
}
|
||||||
|
ref.invalidateSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadClient {
|
||||||
|
int? id;
|
||||||
|
bool? enable;
|
||||||
|
String? name;
|
||||||
|
String? implementation;
|
||||||
|
String? url;
|
||||||
|
bool? removeCompletedDownloads;
|
||||||
|
bool? removeFailedDownloads;
|
||||||
|
|
||||||
|
DownloadClient(
|
||||||
|
{this.id,
|
||||||
|
this.enable,
|
||||||
|
this.name,
|
||||||
|
this.implementation,
|
||||||
|
this.url,
|
||||||
|
this.removeCompletedDownloads,
|
||||||
|
this.removeFailedDownloads});
|
||||||
|
|
||||||
|
DownloadClient.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
enable = json['enable'];
|
||||||
|
name = json['name'];
|
||||||
|
implementation = json['implementation'];
|
||||||
|
url = json['url'];
|
||||||
|
removeCompletedDownloads = json['remove_completed_downloads'];
|
||||||
|
removeFailedDownloads = json['remove_failed_downloads'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['id'] = this.id;
|
||||||
|
data['enable'] = this.enable;
|
||||||
|
data['name'] = this.name;
|
||||||
|
data['implementation'] = this.implementation;
|
||||||
|
data['url'] = this.url;
|
||||||
|
data['remove_completed_downloads'] = this.removeCompletedDownloads;
|
||||||
|
data['remove_failed_downloads'] = this.removeFailedDownloads;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
import 'package:ui/server_response.dart';
|
import 'package:ui/server_response.dart';
|
||||||
|
|
||||||
final welcomePageDataProvider = FutureProvider((ref) async {
|
final welcomePageDataProvider = FutureProvider((ref) async {
|
||||||
@@ -43,107 +43,6 @@ class TvSeries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmdbApiSettingProvider = FutureProvider(
|
|
||||||
(ref) async {
|
|
||||||
final dio = Dio();
|
|
||||||
var resp = await dio
|
|
||||||
.get(APIs.settingsUrl, queryParameters: {"key": APIs.tmdbApiKey});
|
|
||||||
var rrr = resp.data as Map<String, dynamic>;
|
|
||||||
var data = rrr["data"] as Map<String, dynamic>;
|
|
||||||
var key = data[APIs.tmdbApiKey] as String;
|
|
||||||
|
|
||||||
return key;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
var indexersProvider = FutureProvider((ref) async {
|
|
||||||
final dio = Dio();
|
|
||||||
var resp = await dio.get(APIs.allIndexersUrl);
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0) {
|
|
||||||
throw sp.message;
|
|
||||||
}
|
|
||||||
List<Indexer> indexers = List.empty(growable: true);
|
|
||||||
for (final item in sp.data as List) {
|
|
||||||
indexers.add(Indexer.fromJson(item));
|
|
||||||
}
|
|
||||||
return indexers;
|
|
||||||
});
|
|
||||||
|
|
||||||
class Indexer {
|
|
||||||
String? name;
|
|
||||||
String? url;
|
|
||||||
String? apiKey;
|
|
||||||
int? id;
|
|
||||||
|
|
||||||
Indexer({this.name, this.url, this.apiKey});
|
|
||||||
|
|
||||||
Indexer.fromJson(Map<String, dynamic> json) {
|
|
||||||
name = json['name'];
|
|
||||||
url = json['url'];
|
|
||||||
apiKey = json['api_key'];
|
|
||||||
id = json["id"];
|
|
||||||
}
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
|
||||||
data['name'] = this.name;
|
|
||||||
data['url'] = this.url;
|
|
||||||
data['api_key'] = this.apiKey;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dwonloadClientsProvider = FutureProvider((ref) async {
|
|
||||||
final dio = Dio();
|
|
||||||
var resp = await dio.get(APIs.allDownloadClientsUrl);
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0) {
|
|
||||||
throw sp.message;
|
|
||||||
}
|
|
||||||
List<DownloadClient> indexers = List.empty(growable: true);
|
|
||||||
for (final item in sp.data as List) {
|
|
||||||
indexers.add(DownloadClient.fromJson(item));
|
|
||||||
}
|
|
||||||
return indexers;
|
|
||||||
});
|
|
||||||
|
|
||||||
class DownloadClient {
|
|
||||||
int? id;
|
|
||||||
bool? enable;
|
|
||||||
String? name;
|
|
||||||
String? implementation;
|
|
||||||
String? url;
|
|
||||||
bool? removeCompletedDownloads;
|
|
||||||
bool? removeFailedDownloads;
|
|
||||||
|
|
||||||
DownloadClient(
|
|
||||||
{this.id,
|
|
||||||
this.enable,
|
|
||||||
this.name,
|
|
||||||
this.implementation,
|
|
||||||
this.url,
|
|
||||||
this.removeCompletedDownloads,
|
|
||||||
this.removeFailedDownloads});
|
|
||||||
|
|
||||||
DownloadClient.fromJson(Map<String, dynamic> json) {
|
|
||||||
id = json['id'];
|
|
||||||
enable = json['enable'];
|
|
||||||
name = json['name'];
|
|
||||||
implementation = json['implementation'];
|
|
||||||
url = json['url'];
|
|
||||||
removeCompletedDownloads = json['remove_completed_downloads'];
|
|
||||||
removeFailedDownloads = json['remove_failed_downloads'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
|
||||||
data['id'] = this.id;
|
|
||||||
data['enable'] = this.enable;
|
|
||||||
data['name'] = this.name;
|
|
||||||
data['implementation'] = this.implementation;
|
|
||||||
data['url'] = this.url;
|
|
||||||
data['remove_completed_downloads'] = this.removeCompletedDownloads;
|
|
||||||
data['remove_failed_downloads'] = this.removeFailedDownloads;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
import 'package:ui/providers/welcome_data.dart';
|
import 'package:ui/providers/welcome_data.dart';
|
||||||
import 'package:ui/server_response.dart';
|
import 'package:ui/server_response.dart';
|
||||||
import 'package:ui/utils.dart';
|
import 'package:ui/utils.dart';
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/settings.dart';
|
||||||
import 'package:ui/providers/welcome_data.dart';
|
|
||||||
import 'package:ui/server_response.dart';
|
|
||||||
import 'package:ui/utils.dart';
|
import 'package:ui/utils.dart';
|
||||||
|
|
||||||
class SystemSettingsPage extends ConsumerStatefulWidget {
|
class SystemSettingsPage extends ConsumerStatefulWidget {
|
||||||
@@ -18,20 +15,18 @@ class SystemSettingsPage extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
||||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
final GlobalKey _formKey = GlobalKey<FormState>();
|
||||||
|
Future<void>? _pendingTmdb;
|
||||||
List<dynamic> indexers = List.empty();
|
Future<void>? _pendingIndexer;
|
||||||
|
Future<void>? _pendingDownloadClient;
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var key = ref.watch(tmdbApiSettingProvider);
|
var key = ref.watch(tmdbApiSettingProvider);
|
||||||
|
var tmdbSetting = FutureBuilder(
|
||||||
var tmdbSetting = key.when(
|
// We listen to the pending operation, to update the UI accordingly.
|
||||||
data: (data) => Container(
|
future: _pendingTmdb,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return key.when(
|
||||||
|
data: (value) => Container(
|
||||||
padding: const EdgeInsets.fromLTRB(40, 10, 40, 0),
|
padding: const EdgeInsets.fromLTRB(40, 10, 40, 0),
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey, //设置globalKey,用于后面获取FormState
|
key: _formKey, //设置globalKey,用于后面获取FormState
|
||||||
@@ -40,17 +35,27 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
initialValue: data,
|
initialValue: value,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "TMDB Api Key",
|
labelText: "TMDB Api Key",
|
||||||
icon: Icon(Icons.key),
|
icon: Icon(Icons.key),
|
||||||
),
|
),
|
||||||
//
|
//
|
||||||
validator: (v) {
|
validator: (v) {
|
||||||
return v!.trim().isNotEmpty ? null : "ApiKey 不能为空";
|
return v!.trim().isNotEmpty
|
||||||
|
? null
|
||||||
|
: "ApiKey 不能为空";
|
||||||
},
|
},
|
||||||
onSaved: (newValue) {
|
onSaved: (newValue) {
|
||||||
_submitSettings(context, newValue!);
|
var furture = ref
|
||||||
|
.read(tmdbApiSettingProvider.notifier)
|
||||||
|
.submitSettings(newValue!);
|
||||||
|
setState(() {
|
||||||
|
_pendingTmdb = furture;
|
||||||
|
});
|
||||||
|
if (!showError(snapshot)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
@@ -79,9 +84,14 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
),
|
),
|
||||||
error: (err, trace) => Text("$err"),
|
error: (err, trace) => Text("$err"),
|
||||||
loading: () => const CircularProgressIndicator());
|
loading: () => const CircularProgressIndicator());
|
||||||
|
});
|
||||||
|
|
||||||
var indexers = ref.watch(indexersProvider);
|
var indexers = ref.watch(indexersProvider);
|
||||||
var indexerSetting = indexers.when(
|
var indexerSetting = FutureBuilder(
|
||||||
|
// We listen to the pending operation, to update the UI accordingly.
|
||||||
|
future: _pendingIndexer,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return indexers.when(
|
||||||
data: (value) => GridView.builder(
|
data: (value) => GridView.builder(
|
||||||
itemCount: value.length + 1,
|
itemCount: value.length + 1,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
@@ -97,7 +107,7 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
//splashColor: Colors.blue.withAlpha(30),
|
//splashColor: Colors.blue.withAlpha(30),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showIndexerDetails(context, indexer);
|
showIndexerDetails(snapshot, context, indexer);
|
||||||
},
|
},
|
||||||
child: Center(child: Text(indexer.name!))));
|
child: Center(child: Text(indexer.name!))));
|
||||||
}
|
}
|
||||||
@@ -107,7 +117,7 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
//splashColor: Colors.blue.withAlpha(30),
|
//splashColor: Colors.blue.withAlpha(30),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showIndexerDetails(context, Indexer());
|
showIndexerDetails(snapshot, context, Indexer());
|
||||||
},
|
},
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Icon(Icons.add),
|
child: Icon(Icons.add),
|
||||||
@@ -115,9 +125,14 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
}),
|
}),
|
||||||
error: (err, trace) => Text("$err"),
|
error: (err, trace) => Text("$err"),
|
||||||
loading: () => const CircularProgressIndicator());
|
loading: () => const CircularProgressIndicator());
|
||||||
|
});
|
||||||
|
|
||||||
var downloadClients = ref.watch(dwonloadClientsProvider);
|
var downloadClients = ref.watch(dwonloadClientsProvider);
|
||||||
var downloadSetting = downloadClients.when(
|
var downloadSetting = FutureBuilder(
|
||||||
|
// We listen to the pending operation, to update the UI accordingly.
|
||||||
|
future: _pendingDownloadClient,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return downloadClients.when(
|
||||||
data: (value) => GridView.builder(
|
data: (value) => GridView.builder(
|
||||||
itemCount: value.length + 1,
|
itemCount: value.length + 1,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
@@ -133,7 +148,8 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
//splashColor: Colors.blue.withAlpha(30),
|
//splashColor: Colors.blue.withAlpha(30),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDownloadClientDetails(context, client);
|
showDownloadClientDetails(
|
||||||
|
snapshot, context, client);
|
||||||
},
|
},
|
||||||
child: Center(child: Text(client.name!))));
|
child: Center(child: Text(client.name!))));
|
||||||
}
|
}
|
||||||
@@ -143,7 +159,8 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
//splashColor: Colors.blue.withAlpha(30),
|
//splashColor: Colors.blue.withAlpha(30),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDownloadClientDetails(context, DownloadClient());
|
showDownloadClientDetails(
|
||||||
|
snapshot, context, DownloadClient());
|
||||||
},
|
},
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Icon(Icons.add),
|
child: Icon(Icons.add),
|
||||||
@@ -151,6 +168,7 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
}),
|
}),
|
||||||
error: (err, trace) => Text("$err"),
|
error: (err, trace) => Text("$err"),
|
||||||
loading: () => const CircularProgressIndicator());
|
loading: () => const CircularProgressIndicator());
|
||||||
|
});
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
@@ -179,17 +197,8 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _submitSettings(BuildContext context, String v) async {
|
Future<void> showIndexerDetails(
|
||||||
var resp = await Dio().post(APIs.settingsUrl, data: {APIs.tmdbApiKey: v});
|
AsyncSnapshot<void> snapshot, BuildContext context, Indexer indexer) {
|
||||||
var sp = ServerResponse.fromJson(resp.data as Map<String, dynamic>);
|
|
||||||
if (sp.code != 0) {
|
|
||||||
if (context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showIndexerDetails(BuildContext context, Indexer indexer) {
|
|
||||||
var nameController = TextEditingController(text: indexer.name);
|
var nameController = TextEditingController(text: indexer.name);
|
||||||
var urlController = TextEditingController(text: indexer.url);
|
var urlController = TextEditingController(text: indexer.url);
|
||||||
var apiKeyController = TextEditingController(text: indexer.apiKey);
|
var apiKeyController = TextEditingController(text: indexer.apiKey);
|
||||||
@@ -219,9 +228,19 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
indexer.id == null
|
indexer.id == null
|
||||||
? Text("")
|
? const Text("")
|
||||||
: TextButton(
|
: TextButton(
|
||||||
onPressed: () => {deleteIndexer(context, indexer.id!)},
|
onPressed: () {
|
||||||
|
var f = ref
|
||||||
|
.read(indexersProvider.notifier)
|
||||||
|
.deleteIndexer(indexer.id!);
|
||||||
|
setState(() {
|
||||||
|
_pendingIndexer = f;
|
||||||
|
});
|
||||||
|
if (!showError(snapshot)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: const Text('删除')),
|
child: const Text('删除')),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
@@ -229,8 +248,18 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('确定'),
|
child: const Text('确定'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
addIndexer(context, nameController.text, urlController.text,
|
var f = ref.read(indexersProvider.notifier).addIndexer(
|
||||||
apiKeyController.text);
|
Indexer(
|
||||||
|
name: nameController.text,
|
||||||
|
url: urlController.text,
|
||||||
|
apiKey: apiKeyController.text));
|
||||||
|
setState(() {
|
||||||
|
_pendingIndexer = f;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!showError(snapshot)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -238,36 +267,7 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void addIndexer(
|
Future<void> showDownloadClientDetails(AsyncSnapshot<void> snapshot,
|
||||||
BuildContext context, String name, String url, String apiKey) async {
|
|
||||||
if (name.isEmpty || url.isEmpty || apiKey.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var dio = Dio();
|
|
||||||
var resp = await dio.post(APIs.addIndexerUrl,
|
|
||||||
data: Indexer(name: name, url: url, apiKey: apiKey).toJson());
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0 && context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.refresh(indexersProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteIndexer(BuildContext context, int id) async {
|
|
||||||
var dio = Dio();
|
|
||||||
var resp = await dio.delete("${APIs.delIndexerUrl}$id");
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0 && context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.refresh(indexersProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showDownloadClientDetails(
|
|
||||||
BuildContext context, DownloadClient client) {
|
BuildContext context, DownloadClient client) {
|
||||||
var nameController = TextEditingController(text: client.name);
|
var nameController = TextEditingController(text: client.name);
|
||||||
var urlController = TextEditingController(text: client.url);
|
var urlController = TextEditingController(text: client.url);
|
||||||
@@ -294,10 +294,19 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
client.id == null
|
client.id == null
|
||||||
? Text("")
|
? const Text("")
|
||||||
: TextButton(
|
: TextButton(
|
||||||
onPressed: () =>
|
onPressed: () {
|
||||||
{deleteDownloadClients(context, client.id!)},
|
var f = ref
|
||||||
|
.read(dwonloadClientsProvider.notifier)
|
||||||
|
.deleteDownloadClients(client.id!);
|
||||||
|
setState(() {
|
||||||
|
_pendingDownloadClient = f;
|
||||||
|
});
|
||||||
|
if (!showError(snapshot)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: const Text('删除')),
|
child: const Text('删除')),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
@@ -305,8 +314,16 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('确定'),
|
child: const Text('确定'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
addDownloadClients(
|
var f = ref
|
||||||
context, nameController.text, urlController.text);
|
.read(dwonloadClientsProvider.notifier)
|
||||||
|
.addDownloadClients(
|
||||||
|
nameController.text, urlController.text);
|
||||||
|
setState(() {
|
||||||
|
_pendingDownloadClient = f;
|
||||||
|
});
|
||||||
|
if (!showError(snapshot)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -314,33 +331,13 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDownloadClients(BuildContext context, String name, String url) async {
|
bool showError(AsyncSnapshot<void> snapshot) {
|
||||||
if (name.isEmpty || url.isEmpty) {
|
final isErrored = snapshot.hasError &&
|
||||||
return;
|
snapshot.connectionState != ConnectionState.waiting;
|
||||||
|
if (isErrored) {
|
||||||
|
Utils.showSnakeBar(context, "当前操作出错: ${snapshot.error}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
var dio = Dio();
|
return false;
|
||||||
var resp = await dio.post(APIs.addDownloadClientUrl, data: {
|
|
||||||
"name": name,
|
|
||||||
"url": url,
|
|
||||||
});
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0 && context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.refresh(dwonloadClientsProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteDownloadClients(BuildContext context, int id) async {
|
|
||||||
var dio = Dio();
|
|
||||||
var resp = await dio.delete("${APIs.delDownloadClientUrl}$id");
|
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0 && context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.refresh(dwonloadClientsProvider);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
import 'package:ui/providers/series_details.dart';
|
import 'package:ui/providers/series_details.dart';
|
||||||
import 'package:ui/server_response.dart';
|
|
||||||
import 'package:ui/utils.dart';
|
import 'package:ui/utils.dart';
|
||||||
|
|
||||||
class TvDetailsPage extends ConsumerStatefulWidget {
|
class TvDetailsPage extends ConsumerStatefulWidget {
|
||||||
@@ -27,6 +25,7 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
|||||||
final String seriesId;
|
final String seriesId;
|
||||||
|
|
||||||
_TvDetailsPageState({required this.seriesId});
|
_TvDetailsPageState({required this.seriesId});
|
||||||
|
Future<String>? _pendingFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -36,6 +35,10 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var seriesDetails = ref.watch(seriesDetailsProvider(seriesId));
|
var seriesDetails = ref.watch(seriesDetailsProvider(seriesId));
|
||||||
|
return FutureBuilder(
|
||||||
|
// We listen to the pending operation, to update the UI accordingly.
|
||||||
|
future: _pendingFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
return seriesDetails.when(
|
return seriesDetails.when(
|
||||||
data: (details) {
|
data: (details) {
|
||||||
Map<int, List<Widget>> m = Map();
|
Map<int, List<Widget>> m = Map();
|
||||||
@@ -58,9 +61,20 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
|||||||
Text("${ep.title}", textAlign: TextAlign.left),
|
Text("${ep.title}", textAlign: TextAlign.left),
|
||||||
const Expanded(child: Text("")),
|
const Expanded(child: Text("")),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
_searchAndDownload(context, seriesId, ep.seasonNumber!,
|
var f = ref
|
||||||
|
.read(
|
||||||
|
seriesDetailsProvider(seriesId).notifier)
|
||||||
|
.searchAndDownload(seriesId, ep.seasonNumber!,
|
||||||
ep.episodeNumber!);
|
ep.episodeNumber!);
|
||||||
|
setState(() {
|
||||||
|
_pendingFuture = f;
|
||||||
|
});
|
||||||
|
if (!Utils.showError(context, snapshot)) {
|
||||||
|
var name = await f;
|
||||||
|
Utils.showSnakeBar(
|
||||||
|
context, "开始下载: $name");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.search))
|
icon: const Icon(Icons.search))
|
||||||
],
|
],
|
||||||
@@ -106,7 +120,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
|||||||
Text(
|
Text(
|
||||||
"${details!.name}",
|
"${details!.name}",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14, fontWeight: FontWeight.bold),
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Text(""),
|
const Text(""),
|
||||||
Text(details!.overview!)
|
Text(details!.overview!)
|
||||||
@@ -126,23 +141,6 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
|||||||
return Text("$err");
|
return Text("$err");
|
||||||
},
|
},
|
||||||
loading: () => const CircularProgressIndicator());
|
loading: () => const CircularProgressIndicator());
|
||||||
}
|
|
||||||
|
|
||||||
void _searchAndDownload(BuildContext context, String seriesId, int seasonNum,
|
|
||||||
int episodeNum) async {
|
|
||||||
var resp = await Dio().post(APIs.searchAndDownloadUrl, data: {
|
|
||||||
"id": int.parse(seriesId),
|
|
||||||
"season": seasonNum,
|
|
||||||
"episode": episodeNum,
|
|
||||||
});
|
});
|
||||||
var sp = ServerResponse.fromJson(resp.data);
|
|
||||||
if (sp.code != 0 && context.mounted) {
|
|
||||||
Utils.showAlertDialog(context, sp.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var name = (sp.data as Map<String, dynamic>)["name"];
|
|
||||||
if (context.mounted) {
|
|
||||||
Utils.showSnakeBar(context, "$name 开始下载...");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,4 +31,14 @@ class Utils {
|
|||||||
static showSnakeBar(BuildContext context, String msg) {
|
static showSnakeBar(BuildContext context, String msg) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool showError(BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
final isErrored = snapshot.hasError &&
|
||||||
|
snapshot.connectionState != ConnectionState.waiting;
|
||||||
|
if (isErrored) {
|
||||||
|
Utils.showSnakeBar(context, "当前操作出错: ${snapshot.error}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:ui/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
import 'package:ui/providers/welcome_data.dart';
|
import 'package:ui/providers/welcome_data.dart';
|
||||||
import 'package:ui/tv_details.dart';
|
import 'package:ui/tv_details.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
|
quiver:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
riverpod:
|
riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
go_router: ^14.2.0
|
go_router: ^14.2.0
|
||||||
flutter_riverpod: ^2.5.1
|
flutter_riverpod: ^2.5.1
|
||||||
|
quiver: ^3.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user