mirror of
https://github.com/simon-ding/polaris.git
synced 2026-03-08 10:40:45 +08:00
code refactor & improve search page
This commit is contained in:
12
db/const.go
12
db/const.go
@@ -33,16 +33,4 @@ const (
|
||||
|
||||
type ResolutionType string
|
||||
|
||||
const (
|
||||
Any ResolutionType = "any"
|
||||
R720p ResolutionType = "720p"
|
||||
R1080p ResolutionType = "1080p"
|
||||
R4k ResolutionType = "4k"
|
||||
)
|
||||
|
||||
func (r ResolutionType) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
|
||||
const JwtSerectKey = "jwt_secrect_key"
|
||||
1
db/db.go
1
db/db.go
@@ -146,6 +146,7 @@ func (c *Client) AddMediaWatchlist(m *ent.Media, episodes []int) (*ent.Media, er
|
||||
SetAirDate(m.AirDate).
|
||||
SetResolution(m.Resolution).
|
||||
SetTargetDir(m.TargetDir).
|
||||
SetDownloadHistoryEpisodes(m.DownloadHistoryEpisodes).
|
||||
AddEpisodeIDs(episodes...).
|
||||
Save(context.TODO())
|
||||
return r, err
|
||||
|
||||
@@ -124,7 +124,7 @@ const DefaultResolution = Resolution1080p
|
||||
const (
|
||||
Resolution720p Resolution = "720p"
|
||||
Resolution1080p Resolution = "1080p"
|
||||
Resolution4k Resolution = "4k"
|
||||
Resolution2160p Resolution = "2160p"
|
||||
)
|
||||
|
||||
func (r Resolution) String() string {
|
||||
@@ -134,7 +134,7 @@ func (r Resolution) String() string {
|
||||
// ResolutionValidator is a validator for the "resolution" field enum values. It is called by the builders before save.
|
||||
func ResolutionValidator(r Resolution) error {
|
||||
switch r {
|
||||
case Resolution720p, Resolution1080p, Resolution4k:
|
||||
case Resolution720p, Resolution1080p, Resolution2160p:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("media: invalid enum value for resolution field: %q", r)
|
||||
|
||||
@@ -100,7 +100,7 @@ var (
|
||||
{Name: "overview", Type: field.TypeString},
|
||||
{Name: "created_at", Type: field.TypeTime},
|
||||
{Name: "air_date", Type: field.TypeString, Default: ""},
|
||||
{Name: "resolution", Type: field.TypeEnum, Enums: []string{"720p", "1080p", "4k"}, Default: "1080p"},
|
||||
{Name: "resolution", Type: field.TypeEnum, Enums: []string{"720p", "1080p", "2160p"}, Default: "1080p"},
|
||||
{Name: "storage_id", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "target_dir", Type: field.TypeString, Nullable: true},
|
||||
{Name: "download_history_episodes", Type: field.TypeBool, Nullable: true, Default: false},
|
||||
|
||||
@@ -25,7 +25,7 @@ func (Media) Fields() []ent.Field {
|
||||
field.String("overview"),
|
||||
field.Time("created_at").Default(time.Now()),
|
||||
field.String("air_date").Default(""),
|
||||
field.Enum("resolution").Values("720p", "1080p", "4k").Default("1080p"),
|
||||
field.Enum("resolution").Values("720p", "1080p", "2160p").Default("1080p"),
|
||||
field.Int("storage_id").Optional(),
|
||||
field.String("target_dir").Optional(),
|
||||
field.Bool("download_history_episodes").Optional().Default(false).Comment("tv series only"),
|
||||
|
||||
@@ -91,7 +91,6 @@ func (s *Server) Serve() error {
|
||||
tv.GET("/movie/watchlist", HttpHandler(s.GetMovieWatchlist))
|
||||
tv.GET("/record/:id", HttpHandler(s.GetMediaDetails))
|
||||
tv.DELETE("/record/:id", HttpHandler(s.DeleteFromWatchlist))
|
||||
tv.GET("/resolutions", HttpHandler(s.GetAvailableResolutions))
|
||||
tv.GET("/suggest/tv/:tmdb_id", HttpHandler(s.SuggestedSeriesFolderName))
|
||||
tv.GET("/suggest/movie/:tmdb_id", HttpHandler(s.SuggestedMovieFolderName))
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
return nil, errors.Wrap(err, "bind query")
|
||||
}
|
||||
log.Debugf("add tv watchlist input %+v", in)
|
||||
if in.Folder == "" {
|
||||
return nil, errors.New("folder should be provided")
|
||||
}
|
||||
@@ -320,14 +321,6 @@ func (s *Server) GetMediaDetails(c *gin.Context) (interface{}, error) {
|
||||
return MediaDetails{MediaDetails: detail, Storage: &st.Storage}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetAvailableResolutions(c *gin.Context) (interface{}, error) {
|
||||
return []db.ResolutionType{
|
||||
db.R720p,
|
||||
db.R1080p,
|
||||
db.R4k,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteFromWatchlist(c *gin.Context) (interface{}, error) {
|
||||
ids := c.Param("id")
|
||||
id, err := strconv.Atoi(ids)
|
||||
|
||||
@@ -121,7 +121,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||
|
||||
return ProviderScope(
|
||||
child: MaterialApp.router(
|
||||
title: 'Polaris 影视追踪',
|
||||
title: 'Polaris 影视追踪下载',
|
||||
theme: ThemeData(
|
||||
fontFamily: "NotoSansSC",
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
|
||||
@@ -64,6 +64,7 @@ class SeriesDetails {
|
||||
String? airDate;
|
||||
String? mediaType;
|
||||
Storage? storage;
|
||||
String? targetDir;
|
||||
|
||||
SeriesDetails(
|
||||
{this.id,
|
||||
@@ -78,6 +79,7 @@ class SeriesDetails {
|
||||
this.airDate,
|
||||
this.episodes,
|
||||
this.mediaType,
|
||||
this.targetDir,
|
||||
this.storage});
|
||||
|
||||
SeriesDetails.fromJson(Map<String, dynamic> json) {
|
||||
@@ -93,6 +95,7 @@ class SeriesDetails {
|
||||
airDate = json["air_date"];
|
||||
mediaType = json["media_type"];
|
||||
storage = Storage.fromJson(json["storage"]);
|
||||
targetDir = json["target_dir"];
|
||||
if (json['episodes'] != null) {
|
||||
episodes = <Episodes>[];
|
||||
json['episodes'].forEach((v) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:ui/providers/APIs.dart';
|
||||
@@ -148,116 +149,122 @@ class _SearchPageState extends ConsumerState<SearchPage> {
|
||||
}
|
||||
|
||||
Future<void> _showSubmitDialog(BuildContext context, SearchResult item) {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Consumer(
|
||||
builder: (context, ref, _) {
|
||||
String resSelected = "1080p";
|
||||
int storageSelected = 0;
|
||||
var storage = ref.watch(storageSettingProvider);
|
||||
var name = ref.watch(suggestNameDataProvider(
|
||||
(id: item.id!, mediaType: item.mediaType!)));
|
||||
bool downloadHistoryEpisodes = false;
|
||||
|
||||
var pathController = TextEditingController();
|
||||
return AlertDialog(
|
||||
title: Text('添加剧集: ${item.name}'),
|
||||
title: Text('添加: ${item.name}'),
|
||||
content: SizedBox(
|
||||
width: 500,
|
||||
height: 200,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DropdownMenu(
|
||||
width: 200,
|
||||
label: const Text("清晰度"),
|
||||
initialSelection: resSelected,
|
||||
dropdownMenuEntries: const [
|
||||
DropdownMenuEntry(value: "720p", label: "720p"),
|
||||
DropdownMenuEntry(value: "1080p", label: "1080p"),
|
||||
DropdownMenuEntry(value: "4k", label: "4k"),
|
||||
],
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
resSelected = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
storage.when(
|
||||
data: (v) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DropdownMenu(
|
||||
width: 200,
|
||||
label: const Text("存储位置"),
|
||||
initialSelection: storageSelected,
|
||||
dropdownMenuEntries: v
|
||||
.map((s) => DropdownMenuEntry(
|
||||
label: s.name!, value: s.id))
|
||||
.toList(),
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
storageSelected = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
name.when(
|
||||
data: (s) {
|
||||
return storageSelected == 0
|
||||
? const Text("")
|
||||
: () {
|
||||
final storage = v
|
||||
.where((e) =>
|
||||
e.id == storageSelected)
|
||||
.first;
|
||||
final path = item.mediaType ==
|
||||
"tv"
|
||||
? storage.settings!["tv_path"]
|
||||
: storage
|
||||
.settings!["movie_path"];
|
||||
|
||||
pathController.text = s;
|
||||
return SizedBox(
|
||||
//width: 300,
|
||||
child: TextField(
|
||||
controller: pathController,
|
||||
decoration: InputDecoration(
|
||||
labelText: "存储路径",
|
||||
prefix: Text(path)),
|
||||
),
|
||||
);
|
||||
}();
|
||||
},
|
||||
error: (error, stackTrace) =>
|
||||
Text("$error"),
|
||||
loading: () => const MyProgressIndicator(
|
||||
size: 20,
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
initialValue: const {
|
||||
"resolution": "1080p",
|
||||
"storage": null,
|
||||
"folder": "",
|
||||
"history_episodes": false,
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FormBuilderDropdown(
|
||||
name: "resolution",
|
||||
decoration: const InputDecoration(labelText: "清晰度"),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: "720p", child: Text("720p")),
|
||||
DropdownMenuItem(
|
||||
value: "1080p", child: Text("1080p")),
|
||||
DropdownMenuItem(
|
||||
value: "2160p", child: Text("2160p")),
|
||||
],
|
||||
),
|
||||
storage.when(
|
||||
data: (v) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FormBuilderDropdown(
|
||||
onChanged: (v) {
|
||||
setState(
|
||||
() {
|
||||
storageSelected = v!;
|
||||
},
|
||||
);
|
||||
},
|
||||
name: "storage",
|
||||
decoration: const InputDecoration(
|
||||
labelText: "存储位置"),
|
||||
items: v
|
||||
.map((s) => DropdownMenuItem(
|
||||
value: s.id,
|
||||
child: Text(s.name!)))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
item.mediaType == "tv"
|
||||
? SizedBox(
|
||||
width: 250,
|
||||
child: CheckboxListTile(
|
||||
name.when(
|
||||
data: (s) {
|
||||
return storageSelected == 0
|
||||
? const Text("")
|
||||
: () {
|
||||
final storage = v
|
||||
.where((e) =>
|
||||
e.id == storageSelected)
|
||||
.first;
|
||||
final path =
|
||||
item.mediaType == "tv"
|
||||
? storage.tvPath
|
||||
: storage.moviePath;
|
||||
|
||||
pathController.text = s;
|
||||
return SizedBox(
|
||||
//width: 300,
|
||||
child: FormBuilderTextField(
|
||||
name: "folder",
|
||||
controller: pathController,
|
||||
decoration: InputDecoration(
|
||||
labelText: "存储路径",
|
||||
prefix: Text(
|
||||
path ?? "unknown")),
|
||||
),
|
||||
);
|
||||
}();
|
||||
},
|
||||
error: (error, stackTrace) =>
|
||||
Text("$error"),
|
||||
loading: () => const MyProgressIndicator(
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
item.mediaType == "tv"
|
||||
? SizedBox(
|
||||
width: 250,
|
||||
child: FormBuilderCheckbox(
|
||||
name: "history_episodes",
|
||||
title: const Text("是否下载往期剧集"),
|
||||
value: downloadHistoryEpisodes,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
downloadHistoryEpisodes = v!;
|
||||
});
|
||||
}),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
loading: () => const MyProgressIndicator()),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
loading: () => const MyProgressIndicator()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
@@ -276,21 +283,25 @@ class _SearchPageState extends ConsumerState<SearchPage> {
|
||||
),
|
||||
child: const Text('确定'),
|
||||
onPressed: () async {
|
||||
var f = ref
|
||||
.read(searchPageDataProvider(widget.query ?? "")
|
||||
.notifier)
|
||||
.submit2Watchlist(
|
||||
item.id!,
|
||||
storageSelected,
|
||||
resSelected,
|
||||
item.mediaType!,
|
||||
pathController.text,
|
||||
downloadHistoryEpisodes)
|
||||
.then((v) {
|
||||
Navigator.of(context).pop();
|
||||
showSnakeBar("添加成功:${item.name}");
|
||||
});
|
||||
showLoadingWithFuture(f);
|
||||
if (_formKey.currentState!.saveAndValidate()) {
|
||||
final values = _formKey.currentState!.value;
|
||||
//print(values);
|
||||
var f = ref
|
||||
.read(searchPageDataProvider(widget.query ?? "")
|
||||
.notifier)
|
||||
.submit2Watchlist(
|
||||
item.id!,
|
||||
values["storage"],
|
||||
values["resolution"],
|
||||
item.mediaType!,
|
||||
values["folder"],
|
||||
values["history_episodes"] ?? false)
|
||||
.then((v) {
|
||||
Navigator.of(context).pop();
|
||||
showSnakeBar("添加成功:${item.name}");
|
||||
});
|
||||
showLoadingWithFuture(f);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -25,7 +25,8 @@ class _DetailCardState extends ConsumerState<DetailCard> {
|
||||
margin: const EdgeInsets.all(4),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height*0.4),
|
||||
constraints:
|
||||
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.4),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
@@ -54,7 +55,6 @@ class _DetailCardState extends ConsumerState<DetailCard> {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(""),
|
||||
Row(
|
||||
@@ -64,7 +64,13 @@ class _DetailCardState extends ConsumerState<DetailCard> {
|
||||
width: 30,
|
||||
),
|
||||
Text(
|
||||
"${widget.details.storage!.name} (${widget.details.storage!.implementation})")
|
||||
"${widget.details.storage!.name} (${widget.details.storage!.implementation})"),
|
||||
const SizedBox(
|
||||
width: 30,
|
||||
),
|
||||
Text(
|
||||
"${widget.details.mediaType == "tv" ? widget.details.storage!.tvPath : widget.details.storage!.moviePath}"
|
||||
"${widget.details.targetDir}")
|
||||
],
|
||||
),
|
||||
const Divider(thickness: 1, height: 1),
|
||||
@@ -96,16 +102,20 @@ class _DetailCardState extends ConsumerState<DetailCard> {
|
||||
}
|
||||
|
||||
Widget deleteIcon() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
var f = ref
|
||||
.read(mediaDetailsProvider(widget.details.id.toString()).notifier)
|
||||
.delete()
|
||||
.then((v) => context.go(widget.details.mediaType == "tv"
|
||||
? WelcomePage.routeTv
|
||||
: WelcomePage.routeMoivie));
|
||||
showLoadingWithFuture(f);
|
||||
},
|
||||
icon: const Icon(Icons.delete));
|
||||
return Tooltip(
|
||||
message: widget.details.mediaType == "tv" ? "删除剧集" : "删除电影",
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
var f = ref
|
||||
.read(
|
||||
mediaDetailsProvider(widget.details.id.toString()).notifier)
|
||||
.delete()
|
||||
.then((v) => context.go(widget.details.mediaType == "tv"
|
||||
? WelcomePage.routeTv
|
||||
: WelcomePage.routeMoivie));
|
||||
showLoadingWithFuture(f);
|
||||
},
|
||||
icon: const Icon(Icons.delete)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user