welcome page update

This commit is contained in:
Simon Ding
2024-07-05 17:47:07 +08:00
parent 2cdb2b3332
commit c703552267
16 changed files with 516 additions and 86 deletions

View File

@@ -3,9 +3,11 @@ package db
import (
"context"
"encoding/json"
"fmt"
"polaris/ent"
"polaris/ent/downloadclients"
"polaris/ent/indexers"
"polaris/ent/series"
"polaris/ent/settings"
"polaris/log"
@@ -58,18 +60,23 @@ func (c *Client) GetLanguage() string {
lang := c.GetSetting(SettingLanguage)
log.Infof("get application language: %s", lang)
if lang == "" {
return "zh_CN"
return "zh-CN"
}
return lang
}
func (c *Client) AddWatchlist(path string, detail *tmdb.TVDetails) error {
count := c.ent.Series.Query().Where(series.TmdbID(int(detail.ID))).CountX(context.Background())
if (count > 0) {
return fmt.Errorf("tv series %s already in watchlist", detail.Name)
}
_, err := c.ent.Series.Create().
SetTmdbID(int(detail.ID)).
SetPath(path).
SetOverview(detail.Overview).
SetTitle(detail.Name).
SetOriginalName(detail.OriginalName).
SetPosterPath(detail.PosterPath).
Save(context.TODO())
return err
}

View File

@@ -78,11 +78,12 @@ var (
SeriesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "tmdb_id", Type: field.TypeInt},
{Name: "imdb_id", Type: field.TypeString},
{Name: "imdb_id", Type: field.TypeString, Nullable: true},
{Name: "title", Type: field.TypeString},
{Name: "original_name", Type: field.TypeString},
{Name: "overview", Type: field.TypeString},
{Name: "path", Type: field.TypeString},
{Name: "poster_path", Type: field.TypeString, Nullable: true},
}
// SeriesTable holds the schema information for the "series" table.
SeriesTable = &schema.Table{

View File

@@ -2749,6 +2749,7 @@ type SeriesMutation struct {
original_name *string
overview *string
_path *string
poster_path *string
clearedFields map[string]struct{}
done bool
oldValue func(context.Context) (*Series, error)
@@ -2940,9 +2941,22 @@ func (m *SeriesMutation) OldImdbID(ctx context.Context) (v string, err error) {
return oldValue.ImdbID, nil
}
// ClearImdbID clears the value of the "imdb_id" field.
func (m *SeriesMutation) ClearImdbID() {
m.imdb_id = nil
m.clearedFields[series.FieldImdbID] = struct{}{}
}
// ImdbIDCleared returns if the "imdb_id" field was cleared in this mutation.
func (m *SeriesMutation) ImdbIDCleared() bool {
_, ok := m.clearedFields[series.FieldImdbID]
return ok
}
// ResetImdbID resets all changes to the "imdb_id" field.
func (m *SeriesMutation) ResetImdbID() {
m.imdb_id = nil
delete(m.clearedFields, series.FieldImdbID)
}
// SetTitle sets the "title" field.
@@ -3089,6 +3103,55 @@ func (m *SeriesMutation) ResetPath() {
m._path = nil
}
// SetPosterPath sets the "poster_path" field.
func (m *SeriesMutation) SetPosterPath(s string) {
m.poster_path = &s
}
// PosterPath returns the value of the "poster_path" field in the mutation.
func (m *SeriesMutation) PosterPath() (r string, exists bool) {
v := m.poster_path
if v == nil {
return
}
return *v, true
}
// OldPosterPath returns the old "poster_path" field's value of the Series entity.
// If the Series object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *SeriesMutation) OldPosterPath(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldPosterPath is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldPosterPath requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldPosterPath: %w", err)
}
return oldValue.PosterPath, nil
}
// ClearPosterPath clears the value of the "poster_path" field.
func (m *SeriesMutation) ClearPosterPath() {
m.poster_path = nil
m.clearedFields[series.FieldPosterPath] = struct{}{}
}
// PosterPathCleared returns if the "poster_path" field was cleared in this mutation.
func (m *SeriesMutation) PosterPathCleared() bool {
_, ok := m.clearedFields[series.FieldPosterPath]
return ok
}
// ResetPosterPath resets all changes to the "poster_path" field.
func (m *SeriesMutation) ResetPosterPath() {
m.poster_path = nil
delete(m.clearedFields, series.FieldPosterPath)
}
// Where appends a list predicates to the SeriesMutation builder.
func (m *SeriesMutation) Where(ps ...predicate.Series) {
m.predicates = append(m.predicates, ps...)
@@ -3123,7 +3186,7 @@ func (m *SeriesMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *SeriesMutation) Fields() []string {
fields := make([]string, 0, 6)
fields := make([]string, 0, 7)
if m.tmdb_id != nil {
fields = append(fields, series.FieldTmdbID)
}
@@ -3142,6 +3205,9 @@ func (m *SeriesMutation) Fields() []string {
if m._path != nil {
fields = append(fields, series.FieldPath)
}
if m.poster_path != nil {
fields = append(fields, series.FieldPosterPath)
}
return fields
}
@@ -3162,6 +3228,8 @@ func (m *SeriesMutation) Field(name string) (ent.Value, bool) {
return m.Overview()
case series.FieldPath:
return m.Path()
case series.FieldPosterPath:
return m.PosterPath()
}
return nil, false
}
@@ -3183,6 +3251,8 @@ func (m *SeriesMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldOverview(ctx)
case series.FieldPath:
return m.OldPath(ctx)
case series.FieldPosterPath:
return m.OldPosterPath(ctx)
}
return nil, fmt.Errorf("unknown Series field %s", name)
}
@@ -3234,6 +3304,13 @@ func (m *SeriesMutation) SetField(name string, value ent.Value) error {
}
m.SetPath(v)
return nil
case series.FieldPosterPath:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetPosterPath(v)
return nil
}
return fmt.Errorf("unknown Series field %s", name)
}
@@ -3278,7 +3355,14 @@ func (m *SeriesMutation) AddField(name string, value ent.Value) error {
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *SeriesMutation) ClearedFields() []string {
return nil
var fields []string
if m.FieldCleared(series.FieldImdbID) {
fields = append(fields, series.FieldImdbID)
}
if m.FieldCleared(series.FieldPosterPath) {
fields = append(fields, series.FieldPosterPath)
}
return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
@@ -3291,6 +3375,14 @@ func (m *SeriesMutation) FieldCleared(name string) bool {
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
func (m *SeriesMutation) ClearField(name string) error {
switch name {
case series.FieldImdbID:
m.ClearImdbID()
return nil
case series.FieldPosterPath:
m.ClearPosterPath()
return nil
}
return fmt.Errorf("unknown Series nullable field %s", name)
}
@@ -3316,6 +3408,9 @@ func (m *SeriesMutation) ResetField(name string) error {
case series.FieldPath:
m.ResetPath()
return nil
case series.FieldPosterPath:
m.ResetPosterPath()
return nil
}
return fmt.Errorf("unknown Series field %s", name)
}

View File

@@ -14,11 +14,12 @@ type Series struct {
func (Series) Fields() []ent.Field {
return []ent.Field{
field.Int("tmdb_id"),
field.String("imdb_id"),
field.String("imdb_id").Optional(),
field.String("title"),
field.String("original_name"),
field.String("overview"),
field.String("path"),
field.String("poster_path").Optional(),
}
}

View File

@@ -27,7 +27,9 @@ type Series struct {
// Overview holds the value of the "overview" field.
Overview string `json:"overview,omitempty"`
// Path holds the value of the "path" field.
Path string `json:"path,omitempty"`
Path string `json:"path,omitempty"`
// PosterPath holds the value of the "poster_path" field.
PosterPath string `json:"poster_path,omitempty"`
selectValues sql.SelectValues
}
@@ -38,7 +40,7 @@ func (*Series) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case series.FieldID, series.FieldTmdbID:
values[i] = new(sql.NullInt64)
case series.FieldImdbID, series.FieldTitle, series.FieldOriginalName, series.FieldOverview, series.FieldPath:
case series.FieldImdbID, series.FieldTitle, series.FieldOriginalName, series.FieldOverview, series.FieldPath, series.FieldPosterPath:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
@@ -97,6 +99,12 @@ func (s *Series) assignValues(columns []string, values []any) error {
} else if value.Valid {
s.Path = value.String
}
case series.FieldPosterPath:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field poster_path", values[i])
} else if value.Valid {
s.PosterPath = value.String
}
default:
s.selectValues.Set(columns[i], values[i])
}
@@ -150,6 +158,9 @@ func (s *Series) String() string {
builder.WriteString(", ")
builder.WriteString("path=")
builder.WriteString(s.Path)
builder.WriteString(", ")
builder.WriteString("poster_path=")
builder.WriteString(s.PosterPath)
builder.WriteByte(')')
return builder.String()
}

View File

@@ -23,6 +23,8 @@ const (
FieldOverview = "overview"
// FieldPath holds the string denoting the path field in the database.
FieldPath = "path"
// FieldPosterPath holds the string denoting the poster_path field in the database.
FieldPosterPath = "poster_path"
// Table holds the table name of the series in the database.
Table = "series"
)
@@ -36,6 +38,7 @@ var Columns = []string{
FieldOriginalName,
FieldOverview,
FieldPath,
FieldPosterPath,
}
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -85,3 +88,8 @@ func ByOverview(opts ...sql.OrderTermOption) OrderOption {
func ByPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPath, opts...).ToFunc()
}
// ByPosterPath orders the results by the poster_path field.
func ByPosterPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPosterPath, opts...).ToFunc()
}

View File

@@ -83,6 +83,11 @@ func Path(v string) predicate.Series {
return predicate.Series(sql.FieldEQ(FieldPath, v))
}
// PosterPath applies equality check predicate on the "poster_path" field. It's identical to PosterPathEQ.
func PosterPath(v string) predicate.Series {
return predicate.Series(sql.FieldEQ(FieldPosterPath, v))
}
// TmdbIDEQ applies the EQ predicate on the "tmdb_id" field.
func TmdbIDEQ(v int) predicate.Series {
return predicate.Series(sql.FieldEQ(FieldTmdbID, v))
@@ -178,6 +183,16 @@ func ImdbIDHasSuffix(v string) predicate.Series {
return predicate.Series(sql.FieldHasSuffix(FieldImdbID, v))
}
// ImdbIDIsNil applies the IsNil predicate on the "imdb_id" field.
func ImdbIDIsNil() predicate.Series {
return predicate.Series(sql.FieldIsNull(FieldImdbID))
}
// ImdbIDNotNil applies the NotNil predicate on the "imdb_id" field.
func ImdbIDNotNil() predicate.Series {
return predicate.Series(sql.FieldNotNull(FieldImdbID))
}
// ImdbIDEqualFold applies the EqualFold predicate on the "imdb_id" field.
func ImdbIDEqualFold(v string) predicate.Series {
return predicate.Series(sql.FieldEqualFold(FieldImdbID, v))
@@ -448,6 +463,81 @@ func PathContainsFold(v string) predicate.Series {
return predicate.Series(sql.FieldContainsFold(FieldPath, v))
}
// PosterPathEQ applies the EQ predicate on the "poster_path" field.
func PosterPathEQ(v string) predicate.Series {
return predicate.Series(sql.FieldEQ(FieldPosterPath, v))
}
// PosterPathNEQ applies the NEQ predicate on the "poster_path" field.
func PosterPathNEQ(v string) predicate.Series {
return predicate.Series(sql.FieldNEQ(FieldPosterPath, v))
}
// PosterPathIn applies the In predicate on the "poster_path" field.
func PosterPathIn(vs ...string) predicate.Series {
return predicate.Series(sql.FieldIn(FieldPosterPath, vs...))
}
// PosterPathNotIn applies the NotIn predicate on the "poster_path" field.
func PosterPathNotIn(vs ...string) predicate.Series {
return predicate.Series(sql.FieldNotIn(FieldPosterPath, vs...))
}
// PosterPathGT applies the GT predicate on the "poster_path" field.
func PosterPathGT(v string) predicate.Series {
return predicate.Series(sql.FieldGT(FieldPosterPath, v))
}
// PosterPathGTE applies the GTE predicate on the "poster_path" field.
func PosterPathGTE(v string) predicate.Series {
return predicate.Series(sql.FieldGTE(FieldPosterPath, v))
}
// PosterPathLT applies the LT predicate on the "poster_path" field.
func PosterPathLT(v string) predicate.Series {
return predicate.Series(sql.FieldLT(FieldPosterPath, v))
}
// PosterPathLTE applies the LTE predicate on the "poster_path" field.
func PosterPathLTE(v string) predicate.Series {
return predicate.Series(sql.FieldLTE(FieldPosterPath, v))
}
// PosterPathContains applies the Contains predicate on the "poster_path" field.
func PosterPathContains(v string) predicate.Series {
return predicate.Series(sql.FieldContains(FieldPosterPath, v))
}
// PosterPathHasPrefix applies the HasPrefix predicate on the "poster_path" field.
func PosterPathHasPrefix(v string) predicate.Series {
return predicate.Series(sql.FieldHasPrefix(FieldPosterPath, v))
}
// PosterPathHasSuffix applies the HasSuffix predicate on the "poster_path" field.
func PosterPathHasSuffix(v string) predicate.Series {
return predicate.Series(sql.FieldHasSuffix(FieldPosterPath, v))
}
// PosterPathIsNil applies the IsNil predicate on the "poster_path" field.
func PosterPathIsNil() predicate.Series {
return predicate.Series(sql.FieldIsNull(FieldPosterPath))
}
// PosterPathNotNil applies the NotNil predicate on the "poster_path" field.
func PosterPathNotNil() predicate.Series {
return predicate.Series(sql.FieldNotNull(FieldPosterPath))
}
// PosterPathEqualFold applies the EqualFold predicate on the "poster_path" field.
func PosterPathEqualFold(v string) predicate.Series {
return predicate.Series(sql.FieldEqualFold(FieldPosterPath, v))
}
// PosterPathContainsFold applies the ContainsFold predicate on the "poster_path" field.
func PosterPathContainsFold(v string) predicate.Series {
return predicate.Series(sql.FieldContainsFold(FieldPosterPath, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Series) predicate.Series {
return predicate.Series(sql.AndPredicates(predicates...))

View File

@@ -31,6 +31,14 @@ func (sc *SeriesCreate) SetImdbID(s string) *SeriesCreate {
return sc
}
// SetNillableImdbID sets the "imdb_id" field if the given value is not nil.
func (sc *SeriesCreate) SetNillableImdbID(s *string) *SeriesCreate {
if s != nil {
sc.SetImdbID(*s)
}
return sc
}
// SetTitle sets the "title" field.
func (sc *SeriesCreate) SetTitle(s string) *SeriesCreate {
sc.mutation.SetTitle(s)
@@ -55,6 +63,20 @@ func (sc *SeriesCreate) SetPath(s string) *SeriesCreate {
return sc
}
// SetPosterPath sets the "poster_path" field.
func (sc *SeriesCreate) SetPosterPath(s string) *SeriesCreate {
sc.mutation.SetPosterPath(s)
return sc
}
// SetNillablePosterPath sets the "poster_path" field if the given value is not nil.
func (sc *SeriesCreate) SetNillablePosterPath(s *string) *SeriesCreate {
if s != nil {
sc.SetPosterPath(*s)
}
return sc
}
// Mutation returns the SeriesMutation object of the builder.
func (sc *SeriesCreate) Mutation() *SeriesMutation {
return sc.mutation
@@ -92,9 +114,6 @@ func (sc *SeriesCreate) check() error {
if _, ok := sc.mutation.TmdbID(); !ok {
return &ValidationError{Name: "tmdb_id", err: errors.New(`ent: missing required field "Series.tmdb_id"`)}
}
if _, ok := sc.mutation.ImdbID(); !ok {
return &ValidationError{Name: "imdb_id", err: errors.New(`ent: missing required field "Series.imdb_id"`)}
}
if _, ok := sc.mutation.Title(); !ok {
return &ValidationError{Name: "title", err: errors.New(`ent: missing required field "Series.title"`)}
}
@@ -157,6 +176,10 @@ func (sc *SeriesCreate) createSpec() (*Series, *sqlgraph.CreateSpec) {
_spec.SetField(series.FieldPath, field.TypeString, value)
_node.Path = value
}
if value, ok := sc.mutation.PosterPath(); ok {
_spec.SetField(series.FieldPosterPath, field.TypeString, value)
_node.PosterPath = value
}
return _node, _spec
}

View File

@@ -62,6 +62,12 @@ func (su *SeriesUpdate) SetNillableImdbID(s *string) *SeriesUpdate {
return su
}
// ClearImdbID clears the value of the "imdb_id" field.
func (su *SeriesUpdate) ClearImdbID() *SeriesUpdate {
su.mutation.ClearImdbID()
return su
}
// SetTitle sets the "title" field.
func (su *SeriesUpdate) SetTitle(s string) *SeriesUpdate {
su.mutation.SetTitle(s)
@@ -118,6 +124,26 @@ func (su *SeriesUpdate) SetNillablePath(s *string) *SeriesUpdate {
return su
}
// SetPosterPath sets the "poster_path" field.
func (su *SeriesUpdate) SetPosterPath(s string) *SeriesUpdate {
su.mutation.SetPosterPath(s)
return su
}
// SetNillablePosterPath sets the "poster_path" field if the given value is not nil.
func (su *SeriesUpdate) SetNillablePosterPath(s *string) *SeriesUpdate {
if s != nil {
su.SetPosterPath(*s)
}
return su
}
// ClearPosterPath clears the value of the "poster_path" field.
func (su *SeriesUpdate) ClearPosterPath() *SeriesUpdate {
su.mutation.ClearPosterPath()
return su
}
// Mutation returns the SeriesMutation object of the builder.
func (su *SeriesUpdate) Mutation() *SeriesMutation {
return su.mutation
@@ -168,6 +194,9 @@ func (su *SeriesUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := su.mutation.ImdbID(); ok {
_spec.SetField(series.FieldImdbID, field.TypeString, value)
}
if su.mutation.ImdbIDCleared() {
_spec.ClearField(series.FieldImdbID, field.TypeString)
}
if value, ok := su.mutation.Title(); ok {
_spec.SetField(series.FieldTitle, field.TypeString, value)
}
@@ -180,6 +209,12 @@ func (su *SeriesUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := su.mutation.Path(); ok {
_spec.SetField(series.FieldPath, field.TypeString, value)
}
if value, ok := su.mutation.PosterPath(); ok {
_spec.SetField(series.FieldPosterPath, field.TypeString, value)
}
if su.mutation.PosterPathCleared() {
_spec.ClearField(series.FieldPosterPath, field.TypeString)
}
if n, err = sqlgraph.UpdateNodes(ctx, su.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{series.Label}
@@ -235,6 +270,12 @@ func (suo *SeriesUpdateOne) SetNillableImdbID(s *string) *SeriesUpdateOne {
return suo
}
// ClearImdbID clears the value of the "imdb_id" field.
func (suo *SeriesUpdateOne) ClearImdbID() *SeriesUpdateOne {
suo.mutation.ClearImdbID()
return suo
}
// SetTitle sets the "title" field.
func (suo *SeriesUpdateOne) SetTitle(s string) *SeriesUpdateOne {
suo.mutation.SetTitle(s)
@@ -291,6 +332,26 @@ func (suo *SeriesUpdateOne) SetNillablePath(s *string) *SeriesUpdateOne {
return suo
}
// SetPosterPath sets the "poster_path" field.
func (suo *SeriesUpdateOne) SetPosterPath(s string) *SeriesUpdateOne {
suo.mutation.SetPosterPath(s)
return suo
}
// SetNillablePosterPath sets the "poster_path" field if the given value is not nil.
func (suo *SeriesUpdateOne) SetNillablePosterPath(s *string) *SeriesUpdateOne {
if s != nil {
suo.SetPosterPath(*s)
}
return suo
}
// ClearPosterPath clears the value of the "poster_path" field.
func (suo *SeriesUpdateOne) ClearPosterPath() *SeriesUpdateOne {
suo.mutation.ClearPosterPath()
return suo
}
// Mutation returns the SeriesMutation object of the builder.
func (suo *SeriesUpdateOne) Mutation() *SeriesMutation {
return suo.mutation
@@ -371,6 +432,9 @@ func (suo *SeriesUpdateOne) sqlSave(ctx context.Context) (_node *Series, err err
if value, ok := suo.mutation.ImdbID(); ok {
_spec.SetField(series.FieldImdbID, field.TypeString, value)
}
if suo.mutation.ImdbIDCleared() {
_spec.ClearField(series.FieldImdbID, field.TypeString)
}
if value, ok := suo.mutation.Title(); ok {
_spec.SetField(series.FieldTitle, field.TypeString, value)
}
@@ -383,6 +447,12 @@ func (suo *SeriesUpdateOne) sqlSave(ctx context.Context) (_node *Series, err err
if value, ok := suo.mutation.Path(); ok {
_spec.SetField(series.FieldPath, field.TypeString, value)
}
if value, ok := suo.mutation.PosterPath(); ok {
_spec.SetField(series.FieldPosterPath, field.TypeString, value)
}
if suo.mutation.PosterPathCleared() {
_spec.ClearField(series.FieldPosterPath, field.TypeString)
}
_node = &Series{config: suo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

View File

@@ -1,6 +1,8 @@
package tmdb
import (
"polaris/log"
tmdb "github.com/cyruzin/golang-tmdb"
"github.com/pkg/errors"
)
@@ -23,6 +25,7 @@ func NewClient(apiKey string) (*Client, error) {
}
func (c *Client) GetTvDetails(id int, language string) (*tmdb.TVDetails, error) {
log.Infof("tv id %d, language %s", id, language)
language = wrapLanguage(language)
d, err := c.tmdbClient.GetTVDetails(id, withLangOption(language))
return d, err

View File

@@ -12,6 +12,7 @@ func HttpHandler(f func(*gin.Context) (interface{}, error)) gin.HandlerFunc {
r, err := f(ctx)
if err != nil {
log.Errorf("url %v return error: %v", ctx.Request.URL, err)
ctx.JSON(200, Response{
Code: 1,
Message: fmt.Sprintf("%v", err),

View File

@@ -31,17 +31,19 @@ type addWatchlistIn struct {
func (s *Server) AddWatchlist(c *gin.Context) (interface{}, error) {
var in addWatchlistIn
if err := c.ShouldBindQuery(&in); err != nil {
if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query")
}
detail, err := s.MustTMDB().GetTvDetails(in.ID, s.language)
if err != nil {
return nil, errors.Wrap(err, "get tv detail")
}
log.Infof("find detail for tv id %d: %v", in.ID, detail)
if err := s.db.AddWatchlist(in.RootFolder, detail); err != nil {
return nil, errors.Wrap(err, "add to list")
}
log.Infof("save watchlist success: %s", detail.Name)
for _, season := range detail.Seasons {
seasonId := season.SeasonNumber

View File

@@ -2,6 +2,7 @@ class APIs {
static const _baseUrl = "http://127.0.0.1:8080";
static const searchUrl = "$_baseUrl/api/v1/tv/search";
static const settingsUrl = "$_baseUrl/api/v1/setting/do";
static const watchlistUrl = "$_baseUrl/api/v1/tv/watchlist";
static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/";

View File

@@ -42,7 +42,7 @@ class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
var cards = List<Widget>.empty(growable: true);
for (final item in list) {
var m = item as Map<String, dynamic>;
var m = SearchResult.fromJson(item);
cards.add(Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
@@ -50,7 +50,7 @@ class _SearchPageState extends State<SearchPage> {
//splashColor: Colors.blue.withAlpha(30),
onTap: () {
//showDialog(context: context, builder: builder)
debugPrint('Card tapped.');
_showSubmitDialog(context, m);
},
child: Row(
children: <Widget>[
@@ -59,7 +59,7 @@ class _SearchPageState extends State<SearchPage> {
width: 150,
height: 200,
child: Image.network(
APIs.tmdbImgBaseUrl + m["poster_path"],
APIs.tmdbImgBaseUrl + m.posterPath!,
fit: BoxFit.contain,
),
),
@@ -69,12 +69,12 @@ class _SearchPageState extends State<SearchPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
m["name"],
m.name!,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
const Text(""),
Text(m["overview"])
Text(m.overview!)
],
),
)
@@ -83,25 +83,64 @@ class _SearchPageState extends State<SearchPage> {
)));
}
return Expanded(
child: Column(
children: [
TextField(
autofocus: true,
onSubmitted: (value) => _queryResults(context,value),
decoration: const InputDecoration(
labelText: "搜索",
hintText: "搜索剧集名称",
prefixIcon: Icon(Icons.search)),
),
Expanded(
child: ListView(
children: cards,
))
],
),
return Column(
children: [
TextField(
autofocus: true,
onSubmitted: (value) => _queryResults(context, value),
decoration: const InputDecoration(
labelText: "搜索",
hintText: "搜索剧集名称",
prefixIcon: Icon(Icons.search)),
),
Expanded(
child: ListView(
children: cards,
))
],
);
}
Future<void> _showSubmitDialog(BuildContext context, SearchResult item) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('添加剧集'),
content: Text("是否添加剧集: ${item.name}"),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('取消'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('确定'),
onPressed: () {
_submit2Watchlist(context, item.id!);
Navigator.of(context).pop();
},
),
],
);
});
}
void _submit2Watchlist(BuildContext context, int id) async {
var resp = await Dio()
.post(APIs.watchlistUrl, data: {"id": id, "folder": "/downloads"});
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0 && context.mounted) {
Utils.showAlertDialog(context, sp.message);
}
}
}
class SearchBarApp extends StatefulWidget {
@@ -142,3 +181,50 @@ class _SearchBarAppState extends State<SearchBarApp> {
});
}
}
class SearchResult {
String? originalName;
int? id;
String? name;
int? voteCount;
double? voteAverage;
String? posterPath;
String? firstAirDate;
double? popularity;
List<int>? genreIds;
String? originalLanguage;
String? backdropPath;
String? overview;
List<String>? originCountry;
SearchResult(
{this.originalName,
this.id,
this.name,
this.voteCount,
this.voteAverage,
this.posterPath,
this.firstAirDate,
this.popularity,
this.genreIds,
this.originalLanguage,
this.backdropPath,
this.overview,
this.originCountry});
SearchResult.fromJson(Map<String, dynamic> json) {
originalName = json['original_name'];
id = json['id'];
name = json['name'];
voteCount = json['vote_count'];
voteAverage = json['vote_average'];
posterPath = json['poster_path'];
firstAirDate = json['first_air_date'];
popularity = json['popularity'];
genreIds = json['genre_ids'].cast<int>();
originalLanguage = json['original_language'];
backdropPath = json['backdrop_path'];
overview = json['overview'];
originCountry = json['origin_country'].cast<String>();
}
}

View File

@@ -6,6 +6,8 @@ import 'package:ui/utils.dart';
class SystemSettingsPage extends StatefulWidget {
static const route = "/systemsettings";
const SystemSettingsPage({super.key});
@override
State<StatefulWidget> createState() {
return _SystemSettingsPageState();
@@ -19,8 +21,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
@override
Widget build(BuildContext context) {
_handleRefresh();
return Expanded(
child: Container(
return Container(
padding: const EdgeInsets.fromLTRB(40, 10, 40, 0),
child: RefreshIndicator(
onRefresh: _handleRefresh,
@@ -63,7 +64,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
],
),
)),
));
);
}
Future<void> _handleRefresh() async {

View File

@@ -1,5 +1,7 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:ui/APIs.dart';
import 'package:ui/server_response.dart';
class WelcomePage extends StatefulWidget {
const WelcomePage({super.key});
@@ -16,57 +18,85 @@ class _WeclomePageState extends State<WelcomePage> {
@override
Widget build(BuildContext context) {
var cards = List<Widget>.empty(growable: true);
for (final item in favList) {
var m = item as Map<String, dynamic>;
cards.add(Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
child: InkWell(
//splashColor: Colors.blue.withAlpha(30),
onTap: () {
//showDialog(context: context, builder: builder)
debugPrint('Card tapped.');
},
child: Row(
children: <Widget>[
Flexible(
child: SizedBox(
width: 150,
height: 200,
child: Image.network(
APIs.tmdbImgBaseUrl + m["poster_path"],
fit: BoxFit.contain,
),
),
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
m["name"],
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
_onRefresh();
return GridView.builder(
itemCount: favList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4),
itemBuilder: (context, i) {
var item = TvSeries.fromJson(favList[i]);
return Container(
child: Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
child: InkWell(
//splashColor: Colors.blue.withAlpha(30),
onTap: () {
//showDialog(context: context, builder: builder)
},
child: Column(
children: <Widget>[
Flexible(
child: SizedBox(
width: 300,
height: 600,
child: Image.network(
APIs.tmdbImgBaseUrl + item.posterPath!,
fit: BoxFit.contain,
),
),
),
Flexible(
child: Text(
item.title!,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
)
],
),
const Text(""),
Text(m["overview"])
],
),
)
],
),
)));
}
return Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: Expanded(
child: ListView(
children: cards,
))));
)),
);
});
}
Future<void> _onRefresh() async {}
Future<void> _onRefresh() async {
if (favList.isNotEmpty) {
return;
}
var resp = await Dio().get(APIs.watchlistUrl);
var sp = ServerResponse.fromJson(resp.data);
setState(() {
favList = sp.data as List;
});
}
}
class TvSeries {
int? id;
int? tmdbId;
String? title;
String? originalName;
String? overview;
String? path;
String? posterPath;
TvSeries(
{this.id,
this.tmdbId,
this.title,
this.originalName,
this.overview,
this.path,
this.posterPath});
TvSeries.fromJson(Map<String, dynamic> json) {
id = json['id'];
tmdbId = json['tmdb_id'];
title = json['title'];
originalName = json['original_name'];
overview = json['overview'];
path = json['path'];
posterPath = json["poster_path"];
}
}