From c70355226739ffb5c595b83acf090ee3e59b6e1b Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Fri, 5 Jul 2024 17:47:07 +0800 Subject: [PATCH] welcome page update --- db/db.go | 9 ++- ent/migrate/schema.go | 3 +- ent/mutation.go | 99 ++++++++++++++++++++++++++- ent/schema/series.go | 3 +- ent/series.go | 15 ++++- ent/series/series.go | 8 +++ ent/series/where.go | 90 +++++++++++++++++++++++++ ent/series_create.go | 29 +++++++- ent/series_update.go | 70 +++++++++++++++++++ pkg/tmdb/tmdb.go | 3 + server/common.go | 1 + server/watchlist.go | 4 +- ui/lib/APIs.dart | 1 + ui/lib/search.dart | 130 ++++++++++++++++++++++++++++++------ ui/lib/system_settings.dart | 7 +- ui/lib/weclome.dart | 130 ++++++++++++++++++++++-------------- 16 files changed, 516 insertions(+), 86 deletions(-) diff --git a/db/db.go b/db/db.go index 6e1d8c6..d75f248 100644 --- a/db/db.go +++ b/db/db.go @@ -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 } diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index 9a271b6..c65d66f 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -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{ diff --git a/ent/mutation.go b/ent/mutation.go index e17d771..5cddf80 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -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) } diff --git a/ent/schema/series.go b/ent/schema/series.go index 7f8487e..293b131 100644 --- a/ent/schema/series.go +++ b/ent/schema/series.go @@ -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(), } } diff --git a/ent/series.go b/ent/series.go index cfd119e..c953b8e 100644 --- a/ent/series.go +++ b/ent/series.go @@ -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() } diff --git a/ent/series/series.go b/ent/series/series.go index 6324f47..ee091b4 100644 --- a/ent/series/series.go +++ b/ent/series/series.go @@ -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() +} diff --git a/ent/series/where.go b/ent/series/where.go index df0bad0..c45524f 100644 --- a/ent/series/where.go +++ b/ent/series/where.go @@ -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...)) diff --git a/ent/series_create.go b/ent/series_create.go index 390fc0b..f538e89 100644 --- a/ent/series_create.go +++ b/ent/series_create.go @@ -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 } diff --git a/ent/series_update.go b/ent/series_update.go index 4f97042..eedfe39 100644 --- a/ent/series_update.go +++ b/ent/series_update.go @@ -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 diff --git a/pkg/tmdb/tmdb.go b/pkg/tmdb/tmdb.go index 486f2a9..3260e6b 100644 --- a/pkg/tmdb/tmdb.go +++ b/pkg/tmdb/tmdb.go @@ -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 diff --git a/server/common.go b/server/common.go index 729d68b..f5339ac 100644 --- a/server/common.go +++ b/server/common.go @@ -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), diff --git a/server/watchlist.go b/server/watchlist.go index 0efc4db..c17637a 100644 --- a/server/watchlist.go +++ b/server/watchlist.go @@ -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 diff --git a/ui/lib/APIs.dart b/ui/lib/APIs.dart index 89cf206..e14a91f 100644 --- a/ui/lib/APIs.dart +++ b/ui/lib/APIs.dart @@ -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/"; diff --git a/ui/lib/search.dart b/ui/lib/search.dart index 1f4deba..2735415 100644 --- a/ui/lib/search.dart +++ b/ui/lib/search.dart @@ -42,7 +42,7 @@ class _SearchPageState extends State { Widget build(BuildContext context) { var cards = List.empty(growable: true); for (final item in list) { - var m = item as Map; + var m = SearchResult.fromJson(item); cards.add(Card( margin: const EdgeInsets.all(4), clipBehavior: Clip.hardEdge, @@ -50,7 +50,7 @@ class _SearchPageState extends State { //splashColor: Colors.blue.withAlpha(30), onTap: () { //showDialog(context: context, builder: builder) - debugPrint('Card tapped.'); + _showSubmitDialog(context, m); }, child: Row( children: [ @@ -59,7 +59,7 @@ class _SearchPageState extends State { 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 { 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 { ))); } - 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 _showSubmitDialog(BuildContext context, SearchResult item) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('添加剧集'), + content: Text("是否添加剧集: ${item.name}"), + actions: [ + 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 { }); } } + +class SearchResult { + String? originalName; + int? id; + String? name; + int? voteCount; + double? voteAverage; + String? posterPath; + String? firstAirDate; + double? popularity; + List? genreIds; + String? originalLanguage; + String? backdropPath; + String? overview; + List? 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 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(); + originalLanguage = json['original_language']; + backdropPath = json['backdrop_path']; + overview = json['overview']; + originCountry = json['origin_country'].cast(); + } +} diff --git a/ui/lib/system_settings.dart b/ui/lib/system_settings.dart index e908fa3..21be19f 100644 --- a/ui/lib/system_settings.dart +++ b/ui/lib/system_settings.dart @@ -6,6 +6,8 @@ import 'package:ui/utils.dart'; class SystemSettingsPage extends StatefulWidget { static const route = "/systemsettings"; + + const SystemSettingsPage({super.key}); @override State createState() { return _SystemSettingsPageState(); @@ -19,8 +21,7 @@ class _SystemSettingsPageState extends State { @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 { ], ), )), - )); + ); } Future _handleRefresh() async { diff --git a/ui/lib/weclome.dart b/ui/lib/weclome.dart index bab1eec..ecd214c 100644 --- a/ui/lib/weclome.dart +++ b/ui/lib/weclome.dart @@ -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 { @override Widget build(BuildContext context) { - var cards = List.empty(growable: true); - for (final item in favList) { - var m = item as Map; - 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: [ - 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: [ + 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 _onRefresh() async {} + Future _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 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"]; + } }