code refactor & improve search page

This commit is contained in:
Simon Ding
2024-07-31 15:26:34 +08:00
parent 9ba59a7d5a
commit faa603d5df
11 changed files with 153 additions and 148 deletions

View File

@@ -33,16 +33,4 @@ const (
type ResolutionType string 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" const JwtSerectKey = "jwt_secrect_key"

View File

@@ -146,6 +146,7 @@ func (c *Client) AddMediaWatchlist(m *ent.Media, episodes []int) (*ent.Media, er
SetAirDate(m.AirDate). SetAirDate(m.AirDate).
SetResolution(m.Resolution). SetResolution(m.Resolution).
SetTargetDir(m.TargetDir). SetTargetDir(m.TargetDir).
SetDownloadHistoryEpisodes(m.DownloadHistoryEpisodes).
AddEpisodeIDs(episodes...). AddEpisodeIDs(episodes...).
Save(context.TODO()) Save(context.TODO())
return r, err return r, err

View File

@@ -124,7 +124,7 @@ const DefaultResolution = Resolution1080p
const ( const (
Resolution720p Resolution = "720p" Resolution720p Resolution = "720p"
Resolution1080p Resolution = "1080p" Resolution1080p Resolution = "1080p"
Resolution4k Resolution = "4k" Resolution2160p Resolution = "2160p"
) )
func (r Resolution) String() string { 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. // ResolutionValidator is a validator for the "resolution" field enum values. It is called by the builders before save.
func ResolutionValidator(r Resolution) error { func ResolutionValidator(r Resolution) error {
switch r { switch r {
case Resolution720p, Resolution1080p, Resolution4k: case Resolution720p, Resolution1080p, Resolution2160p:
return nil return nil
default: default:
return fmt.Errorf("media: invalid enum value for resolution field: %q", r) return fmt.Errorf("media: invalid enum value for resolution field: %q", r)

View File

@@ -100,7 +100,7 @@ var (
{Name: "overview", Type: field.TypeString}, {Name: "overview", Type: field.TypeString},
{Name: "created_at", Type: field.TypeTime}, {Name: "created_at", Type: field.TypeTime},
{Name: "air_date", Type: field.TypeString, Default: ""}, {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: "storage_id", Type: field.TypeInt, Nullable: true},
{Name: "target_dir", Type: field.TypeString, Nullable: true}, {Name: "target_dir", Type: field.TypeString, Nullable: true},
{Name: "download_history_episodes", Type: field.TypeBool, Nullable: true, Default: false}, {Name: "download_history_episodes", Type: field.TypeBool, Nullable: true, Default: false},

View File

@@ -25,7 +25,7 @@ func (Media) Fields() []ent.Field {
field.String("overview"), field.String("overview"),
field.Time("created_at").Default(time.Now()), field.Time("created_at").Default(time.Now()),
field.String("air_date").Default(""), 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.Int("storage_id").Optional(),
field.String("target_dir").Optional(), field.String("target_dir").Optional(),
field.Bool("download_history_episodes").Optional().Default(false).Comment("tv series only"), field.Bool("download_history_episodes").Optional().Default(false).Comment("tv series only"),

View File

@@ -91,7 +91,6 @@ func (s *Server) Serve() error {
tv.GET("/movie/watchlist", HttpHandler(s.GetMovieWatchlist)) tv.GET("/movie/watchlist", HttpHandler(s.GetMovieWatchlist))
tv.GET("/record/:id", HttpHandler(s.GetMediaDetails)) tv.GET("/record/:id", HttpHandler(s.GetMediaDetails))
tv.DELETE("/record/:id", HttpHandler(s.DeleteFromWatchlist)) 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/tv/:tmdb_id", HttpHandler(s.SuggestedSeriesFolderName))
tv.GET("/suggest/movie/:tmdb_id", HttpHandler(s.SuggestedMovieFolderName)) tv.GET("/suggest/movie/:tmdb_id", HttpHandler(s.SuggestedMovieFolderName))
} }

View File

@@ -69,6 +69,7 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
if err := c.ShouldBindJSON(&in); err != nil { if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query") return nil, errors.Wrap(err, "bind query")
} }
log.Debugf("add tv watchlist input %+v", in)
if in.Folder == "" { if in.Folder == "" {
return nil, errors.New("folder should be provided") 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 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) { func (s *Server) DeleteFromWatchlist(c *gin.Context) (interface{}, error) {
ids := c.Param("id") ids := c.Param("id")
id, err := strconv.Atoi(ids) id, err := strconv.Atoi(ids)

View File

@@ -121,7 +121,7 @@ class _MyAppState extends ConsumerState<MyApp> {
return ProviderScope( return ProviderScope(
child: MaterialApp.router( child: MaterialApp.router(
title: 'Polaris 影视追踪', title: 'Polaris 影视追踪下载',
theme: ThemeData( theme: ThemeData(
fontFamily: "NotoSansSC", fontFamily: "NotoSansSC",
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(

View File

@@ -64,6 +64,7 @@ class SeriesDetails {
String? airDate; String? airDate;
String? mediaType; String? mediaType;
Storage? storage; Storage? storage;
String? targetDir;
SeriesDetails( SeriesDetails(
{this.id, {this.id,
@@ -78,6 +79,7 @@ class SeriesDetails {
this.airDate, this.airDate,
this.episodes, this.episodes,
this.mediaType, this.mediaType,
this.targetDir,
this.storage}); this.storage});
SeriesDetails.fromJson(Map<String, dynamic> json) { SeriesDetails.fromJson(Map<String, dynamic> json) {
@@ -93,6 +95,7 @@ class SeriesDetails {
airDate = json["air_date"]; airDate = json["air_date"];
mediaType = json["media_type"]; mediaType = json["media_type"];
storage = Storage.fromJson(json["storage"]); storage = Storage.fromJson(json["storage"]);
targetDir = json["target_dir"];
if (json['episodes'] != null) { if (json['episodes'] != null) {
episodes = <Episodes>[]; episodes = <Episodes>[];
json['episodes'].forEach((v) { json['episodes'].forEach((v) {

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.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/providers/APIs.dart'; import 'package:ui/providers/APIs.dart';
@@ -148,41 +149,46 @@ class _SearchPageState extends ConsumerState<SearchPage> {
} }
Future<void> _showSubmitDialog(BuildContext context, SearchResult item) { Future<void> _showSubmitDialog(BuildContext context, SearchResult item) {
final _formKey = GlobalKey<FormBuilderState>();
return showDialog<void>( return showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return Consumer( return Consumer(
builder: (context, ref, _) { builder: (context, ref, _) {
String resSelected = "1080p";
int storageSelected = 0; int storageSelected = 0;
var storage = ref.watch(storageSettingProvider); var storage = ref.watch(storageSettingProvider);
var name = ref.watch(suggestNameDataProvider( var name = ref.watch(suggestNameDataProvider(
(id: item.id!, mediaType: item.mediaType!))); (id: item.id!, mediaType: item.mediaType!)));
bool downloadHistoryEpisodes = false;
var pathController = TextEditingController(); var pathController = TextEditingController();
return AlertDialog( return AlertDialog(
title: Text('添加剧集: ${item.name}'), title: Text('添加: ${item.name}'),
content: SizedBox( content: SizedBox(
width: 500, width: 500,
height: 200, height: 200,
child: FormBuilder(
key: _formKey,
initialValue: const {
"resolution": "1080p",
"storage": null,
"folder": "",
"history_episodes": false,
},
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
DropdownMenu( FormBuilderDropdown(
width: 200, name: "resolution",
label: const Text("清晰度"), decoration: const InputDecoration(labelText: "清晰度"),
initialSelection: resSelected, items: const [
dropdownMenuEntries: const [ DropdownMenuItem(
DropdownMenuEntry(value: "720p", label: "720p"), value: "720p", child: Text("720p")),
DropdownMenuEntry(value: "1080p", label: "1080p"), DropdownMenuItem(
DropdownMenuEntry(value: "4k", label: "4k"), value: "1080p", child: Text("1080p")),
DropdownMenuItem(
value: "2160p", child: Text("2160p")),
], ],
onSelected: (value) {
setState(() {
resSelected = value!;
});
},
), ),
storage.when( storage.when(
data: (v) { data: (v) {
@@ -191,19 +197,22 @@ class _SearchPageState extends ConsumerState<SearchPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
DropdownMenu( FormBuilderDropdown(
width: 200, onChanged: (v) {
label: const Text("存储位置"), setState(
initialSelection: storageSelected, () {
dropdownMenuEntries: v storageSelected = v!;
.map((s) => DropdownMenuEntry(
label: s.name!, value: s.id))
.toList(),
onSelected: (value) {
setState(() {
storageSelected = value!;
});
}, },
);
},
name: "storage",
decoration: const InputDecoration(
labelText: "存储位置"),
items: v
.map((s) => DropdownMenuItem(
value: s.id,
child: Text(s.name!)))
.toList(),
), ),
name.when( name.when(
data: (s) { data: (s) {
@@ -214,20 +223,21 @@ class _SearchPageState extends ConsumerState<SearchPage> {
.where((e) => .where((e) =>
e.id == storageSelected) e.id == storageSelected)
.first; .first;
final path = item.mediaType == final path =
"tv" item.mediaType == "tv"
? storage.settings!["tv_path"] ? storage.tvPath
: storage : storage.moviePath;
.settings!["movie_path"];
pathController.text = s; pathController.text = s;
return SizedBox( return SizedBox(
//width: 300, //width: 300,
child: TextField( child: FormBuilderTextField(
name: "folder",
controller: pathController, controller: pathController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: "存储路径", labelText: "存储路径",
prefix: Text(path)), prefix: Text(
path ?? "unknown")),
), ),
); );
}(); }();
@@ -241,14 +251,10 @@ class _SearchPageState extends ConsumerState<SearchPage> {
item.mediaType == "tv" item.mediaType == "tv"
? SizedBox( ? SizedBox(
width: 250, width: 250,
child: CheckboxListTile( child: FormBuilderCheckbox(
name: "history_episodes",
title: const Text("是否下载往期剧集"), title: const Text("是否下载往期剧集"),
value: downloadHistoryEpisodes, ),
onChanged: (v) {
setState(() {
downloadHistoryEpisodes = v!;
});
}),
) )
: const SizedBox(), : const SizedBox(),
], ],
@@ -260,6 +266,7 @@ class _SearchPageState extends ConsumerState<SearchPage> {
], ],
), ),
), ),
),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
@@ -276,21 +283,25 @@ class _SearchPageState extends ConsumerState<SearchPage> {
), ),
child: const Text('确定'), child: const Text('确定'),
onPressed: () async { onPressed: () async {
if (_formKey.currentState!.saveAndValidate()) {
final values = _formKey.currentState!.value;
//print(values);
var f = ref var f = ref
.read(searchPageDataProvider(widget.query ?? "") .read(searchPageDataProvider(widget.query ?? "")
.notifier) .notifier)
.submit2Watchlist( .submit2Watchlist(
item.id!, item.id!,
storageSelected, values["storage"],
resSelected, values["resolution"],
item.mediaType!, item.mediaType!,
pathController.text, values["folder"],
downloadHistoryEpisodes) values["history_episodes"] ?? false)
.then((v) { .then((v) {
Navigator.of(context).pop(); Navigator.of(context).pop();
showSnakeBar("添加成功:${item.name}"); showSnakeBar("添加成功:${item.name}");
}); });
showLoadingWithFuture(f); showLoadingWithFuture(f);
}
}, },
), ),
], ],

View File

@@ -25,7 +25,8 @@ class _DetailCardState extends ConsumerState<DetailCard> {
margin: const EdgeInsets.all(4), margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Container( child: Container(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height*0.4), constraints:
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.4),
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -54,7 +55,6 @@ class _DetailCardState extends ConsumerState<DetailCard> {
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text(""), const Text(""),
Row( Row(
@@ -64,7 +64,13 @@ class _DetailCardState extends ConsumerState<DetailCard> {
width: 30, width: 30,
), ),
Text( 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), const Divider(thickness: 1, height: 1),
@@ -96,16 +102,20 @@ class _DetailCardState extends ConsumerState<DetailCard> {
} }
Widget deleteIcon() { Widget deleteIcon() {
return IconButton( return Tooltip(
message: widget.details.mediaType == "tv" ? "删除剧集" : "删除电影",
child: IconButton(
onPressed: () { onPressed: () {
var f = ref var f = ref
.read(mediaDetailsProvider(widget.details.id.toString()).notifier) .read(
mediaDetailsProvider(widget.details.id.toString()).notifier)
.delete() .delete()
.then((v) => context.go(widget.details.mediaType == "tv" .then((v) => context.go(widget.details.mediaType == "tv"
? WelcomePage.routeTv ? WelcomePage.routeTv
: WelcomePage.routeMoivie)); : WelcomePage.routeMoivie));
showLoadingWithFuture(f); showLoadingWithFuture(f);
}, },
icon: const Icon(Icons.delete)); icon: const Icon(Icons.delete)),
);
} }
} }