feat: support push notification clients

This commit is contained in:
Simon Ding
2024-07-29 15:56:29 +08:00
parent f9d4f851eb
commit fcff47041a
30 changed files with 3004 additions and 90 deletions

View File

@@ -33,6 +33,10 @@ class APIs {
static final logFilesUrl = "$_baseUrl/api/v1/setting/logfiles";
static final aboutUrl = "$_baseUrl/api/v1/setting/about";
static final notifierAllUrl = "$_baseUrl/api/v1/notifier/all";
static final notifierDeleteUrl = "$_baseUrl/api/v1/notifier/id/";
static final notifierAddUrl = "$_baseUrl/api/v1/notifier/add/";
static final tmdbImgBaseUrl = "$_baseUrl/api/v1/posters";
static const tmdbApiKey = "tmdb_api_key";

View File

@@ -0,0 +1,75 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:ui/providers/APIs.dart';
import 'package:ui/providers/server_response.dart';
final notifiersDataProvider =
AsyncNotifierProvider.autoDispose<NotifiersData, List<NotifierData>>(
NotifiersData.new);
class NotifierData {
int? id;
String? name;
String? service;
Map<String, dynamic>? settings;
bool? enabled;
NotifierData({this.id, this.name, this.service, this.enabled, this.settings});
factory NotifierData.fromJson(Map<String, dynamic> json) {
return NotifierData(
id: json["id"],
name: json["name"],
service: json["service"],
enabled: json["enabled"] ?? true,
settings: json["settings"]);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data["name"] = name;
data["service"] = service;
data["enabled"] = enabled;
data["settings"] = jsonEncode(settings);
return data;
}
}
class NotifiersData extends AutoDisposeAsyncNotifier<List<NotifierData>> {
@override
FutureOr<List<NotifierData>> build() async {
final dio = APIs.getDio();
final resp = await dio.get(APIs.notifierAllUrl);
final sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
return sp.data == null
? List.empty()
: (sp.data as List).map((e) => NotifierData.fromJson(e)).toList();
}
Future<void> delete(int id) async {
final dio = APIs.getDio();
final resp = await dio.delete(APIs.notifierDeleteUrl + id.toString());
final sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
ref.invalidateSelf();
}
Future<void> add(NotifierData n) async {
final dio = APIs.getDio();
final resp = await dio.post(APIs.notifierAddUrl, data: n.toJson());
final sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
ref.invalidateSelf();
}
}

View File

@@ -3,6 +3,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:quiver/strings.dart';
import 'package:ui/providers/login.dart';
import 'package:ui/providers/notifier.dart';
import 'package:ui/providers/settings.dart';
import 'package:ui/utils.dart';
import 'package:ui/widgets/progress_indicator.dart';
@@ -52,14 +53,18 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
FormBuilderTextField(
name: "download_dir",
decoration: Commons.requiredTextFieldStyle(
text: "下载路径", icon: const Icon(Icons.folder), helperText: "媒体文件临时下载路径,非最终存储路径"),
text: "下载路径",
icon: const Icon(Icons.folder),
helperText: "媒体文件临时下载路径,非最终存储路径"),
//
validator: FormBuilderValidators.required(),
),
FormBuilderTextField(
name: "proxy",
decoration: const InputDecoration(
labelText: "代理地址", icon: Icon(Icons.folder), helperText: "后台联网代理地址,留空表示不启用代理"),
labelText: "代理地址",
icon: Icon(Icons.folder),
helperText: "后台联网代理地址,留空表示不启用代理"),
),
SizedBox(
width: 300,
@@ -246,40 +251,137 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
expandedAlignment: Alignment.centerLeft,
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
initiallyExpanded: true,
title: const Text("常规设置"),
title: const Text("常规"),
children: [tmdbSetting],
),
ExpansionTile(
expandedAlignment: Alignment.centerLeft,
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
initiallyExpanded: false,
title: const Text("索引器设置"),
title: const Text("索引器"),
children: [indexerSetting],
),
ExpansionTile(
expandedAlignment: Alignment.centerLeft,
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
initiallyExpanded: false,
title: const Text("下载器设置"),
title: const Text("下载器"),
children: [downloadSetting],
),
ExpansionTile(
expandedAlignment: Alignment.centerLeft,
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 50, 0),
initiallyExpanded: false,
title: const Text("存储设置"),
title: const Text("存储"),
children: [storageSetting],
),
ExpansionTile(
expandedAlignment: Alignment.centerLeft,
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 50, 0),
initiallyExpanded: false,
title: const Text("通知客户端"),
children: [notifers()],
),
ExpansionTile(
childrenPadding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
initiallyExpanded: false,
title: const Text("认证设置"),
title: const Text("认证"),
children: [authSetting],
),
],
);
}
Widget notifers() {
final notifierData = ref.watch(notifiersDataProvider);
return notifierData.when(
data: (v) => Wrap(
children: List.generate(v.length + 1, (i) {
if (i < v.length) {
final client = v[i];
return SettingsCard(
child: Text("${client.name!} (${client.service})"),
onTap: () => showNotifierDetails(client),
);
}
return SettingsCard(
onTap: () => showNotifierDetails(NotifierData()),
child: const Icon(Icons.add));
}),
),
error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator());
}
Future<void> showNotifierDetails(NotifierData notifier) {
final _formKey = GlobalKey<FormBuilderState>();
var body = FormBuilder(
key: _formKey,
initialValue: {
"name": notifier.name,
"service": notifier.service,
"enabled": notifier.enabled ?? true,
"app_token":
notifier.settings != null ? notifier.settings!["app_token"] : "",
"user_key":
notifier.settings != null ? notifier.settings!["user_key"] : "",
},
child: Column(
children: [
FormBuilderDropdown(
name: "service",
decoration: const InputDecoration(labelText: "类型"),
items: const [
DropdownMenuItem(value: "pushover", child: Text("Pushover")),
],
),
FormBuilderTextField(
name: "name",
decoration: Commons.requiredTextFieldStyle(text: "名称"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(),
),
FormBuilderTextField(
name: "app_token",
decoration: Commons.requiredTextFieldStyle(text: "APP密钥"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(),
),
FormBuilderTextField(
name: "user_key",
decoration: Commons.requiredTextFieldStyle(text: "用户密钥"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(),
),
FormBuilderSwitch(name: "enabled", title: const Text("启用"))
],
),
);
onDelete() async {
return ref.read(notifiersDataProvider.notifier).delete(notifier.id!);
}
onSubmit() async {
if (_formKey.currentState!.saveAndValidate()) {
var values = _formKey.currentState!.value;
return ref.read(notifiersDataProvider.notifier).add(NotifierData(
name: values["name"],
service: values["service"],
enabled: values["enabled"],
settings: {
"app_token": values["app_token"],
"user_key": values["user_key"]
}));
} else {
throw "validation_error";
}
}
return showSettingDialog(
"通知客户端", notifier.id != null, body, onSubmit, onDelete);
}
Future<void> showIndexerDetails(Indexer indexer) {
final _formKey = GlobalKey<FormBuilderState>();
@@ -564,7 +666,9 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
"user": values["user"],
"password": values["password"],
"change_file_hash":
(values["change_file_hash"]??false) as bool ? "true" : "false"
(values["change_file_hash"] ?? false) as bool
? "true"
: "false"
},
));
} else {