mirror of
https://github.com/simon-ding/polaris.git
synced 2026-03-18 17:27:31 +08:00
feat: support prowlarr connection
This commit is contained in:
@@ -17,6 +17,7 @@ const (
|
||||
SetttingSizeLimiter = "size_limiter"
|
||||
SettingTvNamingFormat = "tv_naming_format"
|
||||
SettingMovieNamingFormat = "movie_naming_format"
|
||||
SettingProwlarrInfo = "prowlarr_info"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -60,3 +61,8 @@ type Limiter struct {
|
||||
Max int `json:"max"`
|
||||
Min int `json:"min"`
|
||||
}
|
||||
|
||||
type ProwlarrSetting struct {
|
||||
ApiKey string `json:"api_key"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
21
db/db.go
21
db/db.go
@@ -660,3 +660,24 @@ func (c *Client) CleanAllDanglingEpisodes() error {
|
||||
func (c *Client) AddBlacklistItem(item *ent.Blacklist) error {
|
||||
return c.ent.Blacklist.Create().SetType(item.Type).SetValue(item.Value).SetNotes(item.Notes).Exec(context.Background())
|
||||
}
|
||||
|
||||
|
||||
func (c *Client) GetProwlarrSetting() (*ProwlarrSetting, error) {
|
||||
s := c.GetSetting(SettingProwlarrInfo)
|
||||
if s == "" {
|
||||
return nil, errors.New("prowlarr setting not set")
|
||||
}
|
||||
var se ProwlarrSetting
|
||||
if err := json.Unmarshal([]byte(s), &se); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &se, nil
|
||||
}
|
||||
|
||||
func (c *Client) SaveProwlarrSetting(se *ProwlarrSetting) error {
|
||||
data, err := json.Marshal(se)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetSetting(SettingProwlarrInfo, string(data))
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -21,6 +21,7 @@ require (
|
||||
github.com/ncruces/go-sqlite3 v0.18.4
|
||||
github.com/nikoksr/notify v1.0.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golift.io/starr v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
10
go.sum
10
go.sum
@@ -216,8 +216,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -251,8 +249,6 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8=
|
||||
github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
@@ -310,8 +306,6 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
@@ -453,10 +447,10 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI=
|
||||
golift.io/starr v1.0.0/go.mod h1:xnUwp4vK62bDvozW9QHUYc08m6kjwaZnGw3Db65fQHw=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
||||
@@ -37,6 +37,7 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var items []importlist.Item
|
||||
doc.Find("div[class=doulist-item]").Each(func(i int, selection *goquery.Selection) {
|
||||
titleDiv := selection.Find("div[class=title]")
|
||||
link := titleDiv.Find("div>a")
|
||||
@@ -64,18 +65,26 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := parseDetailPage(strings.TrimSpace(href))
|
||||
if err != nil {
|
||||
log.Errorf("get detail page: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
item := importlist.Item{
|
||||
Title: strings.TrimSpace(link.Text()),
|
||||
Year: year,
|
||||
}
|
||||
items = append(items, item)
|
||||
_ = item
|
||||
println(link.Text(), href)
|
||||
//println(link.Text(), href)
|
||||
})
|
||||
return nil, nil
|
||||
|
||||
return &importlist.Response{Items: items}, nil
|
||||
}
|
||||
|
||||
func parseDetailPage(url string) (string, error) {
|
||||
println(url)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -95,6 +104,14 @@ func parseDetailPage(url string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
doc.Find("div[class='subject clearfix']").Each(func(i int, se *goquery.Selection) {
|
||||
println(se.Text())
|
||||
se.Children().Get(1)
|
||||
imdb := se.Find("div[class='info']").First().Children().Last()
|
||||
println(imdb.Text())
|
||||
})
|
||||
|
||||
_ = doc
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func TestParseDoulist(t *testing.T) {
|
||||
r, err := ParseDoulist("https://www.douban.com/doulist/166422/")
|
||||
r, err := ParseDoulist("https://www.douban.com/doulist/81580/")
|
||||
log.Info(r, err)
|
||||
}
|
||||
|
||||
65
pkg/prowlarr/prowlarr.go
Normal file
65
pkg/prowlarr/prowlarr.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package prowlarr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"polaris/db"
|
||||
"polaris/ent"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golift.io/starr"
|
||||
"golift.io/starr/prowlarr"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
p *prowlarr.Prowlarr
|
||||
apiKey string
|
||||
url string
|
||||
}
|
||||
|
||||
func New(apiKey, url string) *Client {
|
||||
c := starr.New(apiKey, url, 10*time.Second)
|
||||
p := prowlarr.New(c)
|
||||
return &Client{p: p, apiKey: apiKey, url: url}
|
||||
}
|
||||
|
||||
func (c *Client) GetIndexers() ([]*db.TorznabInfo, error) {
|
||||
ins, err := c.p.GetIndexers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var indexers []*db.TorznabInfo
|
||||
for _, in := range ins {
|
||||
if !in.Enable {
|
||||
continue
|
||||
}
|
||||
seedRatio := 0.0
|
||||
for _, f := range in.Fields {
|
||||
if f.Name == "torrentBaseSettings.seedRatio" && f.Value != nil {
|
||||
if r, ok := f.Value.(float64); ok {
|
||||
seedRatio = r
|
||||
}
|
||||
}
|
||||
}
|
||||
setting := db.TorznabSetting{
|
||||
URL: fmt.Sprintf("%s/%d/api", strings.TrimSuffix(c.url, "/"), in.ID),
|
||||
ApiKey: c.apiKey,
|
||||
}
|
||||
data, _ := json.Marshal(&setting)
|
||||
|
||||
entIndexer := ent.Indexers{
|
||||
Name: in.Name,
|
||||
Implementation: "torznab",
|
||||
Priority: int(in.Priority),
|
||||
SeedRatio: float32(seedRatio),
|
||||
Settings: string(data),
|
||||
}
|
||||
|
||||
indexers = append(indexers, &db.TorznabInfo{
|
||||
Indexers: &entIndexer,
|
||||
TorznabSetting: setting,
|
||||
})
|
||||
}
|
||||
return indexers, nil
|
||||
}
|
||||
13
pkg/prowlarr/prowlarr_test.go
Normal file
13
pkg/prowlarr/prowlarr_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package prowlarr
|
||||
|
||||
import (
|
||||
"polaris/log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test111(t *testing.T) {
|
||||
c := New("", "http://10.0.0.8:9696/")
|
||||
apis , err := c.GetIndexers()
|
||||
log.Infof("errors: %v", err)
|
||||
log.Infof("indexers: %+v", apis[0])
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"polaris/ent/media"
|
||||
"polaris/log"
|
||||
"polaris/pkg/metadata"
|
||||
"polaris/pkg/prowlarr"
|
||||
"polaris/pkg/torznab"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -136,7 +137,7 @@ func torrentSizeOk(detail *db.MediaDetails, torrentSize int, param *SearchParam)
|
||||
}
|
||||
}
|
||||
}
|
||||
return torrentSize > defaultMinSize * multiplier
|
||||
return torrentSize > defaultMinSize*multiplier
|
||||
}
|
||||
|
||||
func seasonEpisodeCount(detail *db.MediaDetails, seasonNum int) int {
|
||||
@@ -230,6 +231,17 @@ func searchWithTorznab(db *db.Client, queries ...string) []torznab.Result {
|
||||
|
||||
var res []torznab.Result
|
||||
allTorznab := db.GetAllTorznabInfo()
|
||||
|
||||
p, err := db.GetProwlarrSetting()
|
||||
if err == nil { //prowlarr exists
|
||||
c := prowlarr.New(p.ApiKey, p.URL)
|
||||
all, err := c.GetIndexers()
|
||||
if err != nil {
|
||||
log.Warnf("get prowlarr all indexer error: %v", err)
|
||||
} else {
|
||||
allTorznab = append(allTorznab, all...)
|
||||
}
|
||||
}
|
||||
resChan := make(chan []torznab.Result)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ func (s *Server) Serve() error {
|
||||
setting.POST("/parse/movie", HttpHandler(s.ParseMovie))
|
||||
setting.POST("/monitoring", HttpHandler(s.ChangeEpisodeMonitoring))
|
||||
setting.POST("/cron/trigger", HttpHandler(s.TriggerCronJob))
|
||||
setting.GET("/prowlarr", HttpHandler(s.GetProwlarrSetting))
|
||||
setting.POST("/prowlarr", HttpHandler(s.SaveProwlarrSetting))
|
||||
}
|
||||
activity := api.Group("/activity")
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"polaris/ent"
|
||||
"polaris/ent/downloadclients"
|
||||
"polaris/log"
|
||||
"polaris/pkg/prowlarr"
|
||||
"polaris/pkg/qbittorrent"
|
||||
"polaris/pkg/torznab"
|
||||
"polaris/pkg/transmission"
|
||||
@@ -303,3 +304,26 @@ func (s *Server) TriggerCronJob(c *gin.Context) (interface{}, error) {
|
||||
}
|
||||
return "success", nil
|
||||
}
|
||||
|
||||
func (s *Server) GetProwlarrSetting(c *gin.Context) (interface{}, error) {
|
||||
se, err :=s.db.GetProwlarrSetting()
|
||||
if err != nil {
|
||||
return &db.ProwlarrSetting{}, nil
|
||||
}
|
||||
return se, nil
|
||||
}
|
||||
func (s *Server) SaveProwlarrSetting(c *gin.Context) (interface{}, error) {
|
||||
var in db.ProwlarrSetting
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := prowlarr.New(in.ApiKey, in.URL)
|
||||
if _, err := client.GetIndexers(); err != nil {
|
||||
return nil, errors.Wrap(err, "connect to prowlarr error")
|
||||
}
|
||||
err := s.db.SaveProwlarrSetting(&in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return "success", nil
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class APIs {
|
||||
static final addImportlistUrl = "$_baseUrl/api/v1/importlist/add";
|
||||
static final deleteImportlistUrl = "$_baseUrl/api/v1/importlist/delete";
|
||||
static final getAllImportlists = "$_baseUrl/api/v1/importlist/";
|
||||
static final prowlarrUrl = "$_baseUrl/api/v1/setting/prowlarr";
|
||||
|
||||
static final notifierAllUrl = "$_baseUrl/api/v1/notifier/all";
|
||||
static final notifierDeleteUrl = "$_baseUrl/api/v1/notifier/id/";
|
||||
|
||||
@@ -25,6 +25,10 @@ var importlistProvider =
|
||||
AsyncNotifierProvider.autoDispose<ImportListData, List<ImportList>>(
|
||||
ImportListData.new);
|
||||
|
||||
var prowlarrSettingDataProvider =
|
||||
AsyncNotifierProvider.autoDispose<ProwlarrSettingData, ProwlarrSetting>(
|
||||
ProwlarrSettingData.new);
|
||||
|
||||
class EditSettingData extends AutoDisposeAsyncNotifier<GeneralSetting> {
|
||||
@override
|
||||
FutureOr<GeneralSetting> build() async {
|
||||
@@ -503,3 +507,38 @@ class ImportListData extends AutoDisposeAsyncNotifier<List<ImportList>> {
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
class ProwlarrSetting {
|
||||
final String apiKey;
|
||||
final String url;
|
||||
ProwlarrSetting({required this.apiKey, required this.url});
|
||||
factory ProwlarrSetting.fromJson(Map<String, dynamic> json) {
|
||||
return ProwlarrSetting(apiKey: json["api_key"], url: json["url"]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> tojson() => {"api_key": apiKey, "url": url};
|
||||
}
|
||||
|
||||
class ProwlarrSettingData extends AutoDisposeAsyncNotifier<ProwlarrSetting> {
|
||||
@override
|
||||
FutureOr<ProwlarrSetting> build() async {
|
||||
final dio = APIs.getDio();
|
||||
var resp = await dio.get(APIs.prowlarrUrl);
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
var se = ProwlarrSetting.fromJson(sp.data);
|
||||
return se;
|
||||
}
|
||||
|
||||
Future<void> save(ProwlarrSetting ps) async {
|
||||
final dio = APIs.getDio();
|
||||
var resp = await dio.post(APIs.prowlarrUrl, data: ps.tojson());
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
72
ui/lib/settings/prowlarr.dart
Normal file
72
ui/lib/settings/prowlarr.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:ui/providers/settings.dart';
|
||||
import 'package:ui/widgets/progress_indicator.dart';
|
||||
import 'package:ui/widgets/utils.dart';
|
||||
import 'package:ui/widgets/widgets.dart';
|
||||
|
||||
class ProwlarrSettingPage extends ConsumerStatefulWidget {
|
||||
const ProwlarrSettingPage({super.key});
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() {
|
||||
return ProwlarrSettingState();
|
||||
}
|
||||
}
|
||||
|
||||
class ProwlarrSettingState extends ConsumerState<ProwlarrSettingPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var ps = ref.watch(prowlarrSettingDataProvider);
|
||||
return ps.when(
|
||||
data: (v) => FormBuilder(
|
||||
key: _formKey, //设置globalKey,用于后面获取FormState
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
initialValue: {"api_key": v.apiKey, "url": v.url},
|
||||
child: Column(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
name: "url",
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Prowlarr URL",
|
||||
icon: Icon(Icons.web),
|
||||
hintText: "http://10.0.0.8:9696"),
|
||||
validator: FormBuilderValidators.required(),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: "api_key",
|
||||
decoration: const InputDecoration(
|
||||
labelText: "API Key",
|
||||
icon: Icon(Icons.web),
|
||||
helperText: "Prowlarr 设置 -> 通用设置 -> 接口密钥"),
|
||||
validator: FormBuilderValidators.required(),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.saveAndValidate()) {
|
||||
var values = _formKey.currentState!.value;
|
||||
var f = ref
|
||||
.read(prowlarrSettingDataProvider.notifier)
|
||||
.save(ProwlarrSetting(
|
||||
apiKey: values["api_key"],
|
||||
url: values["url"]))
|
||||
.then((v) => showSnakeBar("更新成功"));
|
||||
showLoadingWithFuture(f);
|
||||
}
|
||||
},
|
||||
child: const Padding(padding: EdgeInsets.all(10), child: Text("保存"),)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
error: (err, trace) => Text("$err"),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:ui/settings/general.dart';
|
||||
import 'package:ui/settings/importlist.dart';
|
||||
import 'package:ui/settings/indexer.dart';
|
||||
import 'package:ui/settings/notifier.dart';
|
||||
import 'package:ui/settings/prowlarr.dart';
|
||||
import 'package:ui/settings/storage.dart';
|
||||
|
||||
class SystemSettingsPage extends ConsumerStatefulWidget {
|
||||
@@ -25,6 +26,7 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
||||
children: [
|
||||
getExpansionTile("常规", const GeneralSettings()),
|
||||
getExpansionTile("索引器", const IndexerSettings()),
|
||||
getExpansionTile("Prowlarr 设置", const ProwlarrSettingPage()),
|
||||
getExpansionTile("下载器", const DownloaderSettings()),
|
||||
getExpansionTile("存储", const StorageSettings()),
|
||||
getExpansionTile("通知客户端", const NotifierSettings()),
|
||||
|
||||
Reference in New Issue
Block a user