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,116 +149,122 @@ 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: Column( child: FormBuilder(
crossAxisAlignment: CrossAxisAlignment.start, key: _formKey,
children: [ initialValue: const {
DropdownMenu( "resolution": "1080p",
width: 200, "storage": null,
label: const Text("清晰度"), "folder": "",
initialSelection: resSelected, "history_episodes": false,
dropdownMenuEntries: const [ },
DropdownMenuEntry(value: "720p", label: "720p"), child: Column(
DropdownMenuEntry(value: "1080p", label: "1080p"), crossAxisAlignment: CrossAxisAlignment.start,
DropdownMenuEntry(value: "4k", label: "4k"), children: [
], FormBuilderDropdown(
onSelected: (value) { name: "resolution",
setState(() { decoration: const InputDecoration(labelText: "清晰度"),
resSelected = value!; items: const [
}); DropdownMenuItem(
}, value: "720p", child: Text("720p")),
), DropdownMenuItem(
storage.when( value: "1080p", child: Text("1080p")),
data: (v) { DropdownMenuItem(
return StatefulBuilder( value: "2160p", child: Text("2160p")),
builder: (context, setState) { ],
return Column( ),
crossAxisAlignment: CrossAxisAlignment.start, storage.when(
children: [ data: (v) {
DropdownMenu( return StatefulBuilder(
width: 200, builder: (context, setState) {
label: const Text("存储位置"), return Column(
initialSelection: storageSelected, crossAxisAlignment: CrossAxisAlignment.start,
dropdownMenuEntries: v children: [
.map((s) => DropdownMenuEntry( FormBuilderDropdown(
label: s.name!, value: s.id)) onChanged: (v) {
.toList(), setState(
onSelected: (value) { () {
setState(() { storageSelected = v!;
storageSelected = value!; },
}); );
}, },
), name: "storage",
name.when( decoration: const InputDecoration(
data: (s) { labelText: "存储位置"),
return storageSelected == 0 items: v
? const Text("") .map((s) => DropdownMenuItem(
: () { value: s.id,
final storage = v child: Text(s.name!)))
.where((e) => .toList(),
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,
), ),
), name.when(
item.mediaType == "tv" data: (s) {
? SizedBox( return storageSelected == 0
width: 250, ? const Text("")
child: CheckboxListTile( : () {
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("是否下载往期剧集"), title: const Text("是否下载往期剧集"),
value: downloadHistoryEpisodes, ),
onChanged: (v) { )
setState(() { : const SizedBox(),
downloadHistoryEpisodes = v!; ],
}); );
}), });
) },
: const SizedBox(), error: (err, trace) => Text("$err"),
], loading: () => const MyProgressIndicator()),
); ],
}); ),
},
error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator()),
],
), ),
), ),
actions: <Widget>[ actions: <Widget>[
@@ -276,21 +283,25 @@ class _SearchPageState extends ConsumerState<SearchPage> {
), ),
child: const Text('确定'), child: const Text('确定'),
onPressed: () async { onPressed: () async {
var f = ref if (_formKey.currentState!.saveAndValidate()) {
.read(searchPageDataProvider(widget.query ?? "") final values = _formKey.currentState!.value;
.notifier) //print(values);
.submit2Watchlist( var f = ref
item.id!, .read(searchPageDataProvider(widget.query ?? "")
storageSelected, .notifier)
resSelected, .submit2Watchlist(
item.mediaType!, item.id!,
pathController.text, values["storage"],
downloadHistoryEpisodes) values["resolution"],
.then((v) { item.mediaType!,
Navigator.of(context).pop(); values["folder"],
showSnakeBar("添加成功:${item.name}"); values["history_episodes"] ?? false)
}); .then((v) {
showLoadingWithFuture(f); Navigator.of(context).pop();
showSnakeBar("添加成功:${item.name}");
});
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(
onPressed: () { message: widget.details.mediaType == "tv" ? "删除剧集" : "删除电影",
var f = ref child: IconButton(
.read(mediaDetailsProvider(widget.details.id.toString()).notifier) onPressed: () {
.delete() var f = ref
.then((v) => context.go(widget.details.mediaType == "tv" .read(
? WelcomePage.routeTv mediaDetailsProvider(widget.details.id.toString()).notifier)
: WelcomePage.routeMoivie)); .delete()
showLoadingWithFuture(f); .then((v) => context.go(widget.details.mediaType == "tv"
}, ? WelcomePage.routeTv
icon: const Icon(Icons.delete)); : WelcomePage.routeMoivie));
showLoadingWithFuture(f);
},
icon: const Icon(Icons.delete)),
);
} }
} }