Trending, most watched and anticipated movies and series from Trakt
With an empty search box, Movies and Series show one of Trakt's public lists: trending now, most watched this week, anticipated, popular, and for movies the US box office. [ ] switch lists, r reloads. Each entry is looked up in Radarr (tmdb:) or Sonarr (tvdb:), so it behaves like a search result: details, poster, tick when in the library, add, releases. Needs a Trakt client ID: new optional trakt.client_id setting (Config view section "Trakt", ARR_TUI_TRAKT_CLIENT_ID), masked like the API keys and checked by Test. ARR_TUI_TRAKT_URL points at another server for development. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+55
-10
@@ -14,6 +14,7 @@ import (
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/config"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/sonarr"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/trakt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,10 +33,19 @@ type searchView struct {
|
||||
details *detailPane
|
||||
|
||||
// UI-goroutine state.
|
||||
results []result
|
||||
timer *time.Timer
|
||||
cancel context.CancelFunc
|
||||
gen int
|
||||
results []result
|
||||
notes []string // per result, a Trakt list's number ("1.2k watching"); nil for searches
|
||||
placeholder string // shown when there are no results
|
||||
timer *time.Timer
|
||||
cancel context.CancelFunc
|
||||
gen int
|
||||
|
||||
// Trakt lists, shown while the search box is empty (see discover.go).
|
||||
lists []trakt.List
|
||||
listIdx int
|
||||
browsing bool // the results are a Trakt list
|
||||
discover map[int]*discovered
|
||||
loadingList map[int]bool
|
||||
|
||||
// Add-form options, loaded in the background.
|
||||
mu sync.Mutex
|
||||
@@ -52,6 +62,10 @@ func newSearchView(app *App, be backend) *searchView {
|
||||
input: tview.NewInputField().SetLabel("Search: "),
|
||||
list: tview.NewList().ShowSecondaryText(false),
|
||||
details: newDetailPane(app),
|
||||
|
||||
lists: be.lists(),
|
||||
discover: map[int]*discovered{},
|
||||
loadingList: map[int]bool{},
|
||||
}
|
||||
v.list.SetBorder(true).SetTitle(" " + be.noun() + " ")
|
||||
|
||||
@@ -77,6 +91,12 @@ func newSearchView(app *App, be backend) *searchView {
|
||||
v.app.app.SetFocus(v.list)
|
||||
return nil
|
||||
}
|
||||
// While the box is empty a Trakt list is shown, and [ ] switch it (no
|
||||
// search starts with a bracket).
|
||||
if ev.Key() == tcell.KeyRune && (ev.Rune() == '[' || ev.Rune() == ']') && v.browsing && v.input.GetText() == "" {
|
||||
v.switchList(map[rune]int{'[': -1, ']': 1}[ev.Rune()])
|
||||
return nil
|
||||
}
|
||||
return ev
|
||||
})
|
||||
|
||||
@@ -94,6 +114,16 @@ func newSearchView(app *App, be backend) *searchView {
|
||||
case '/':
|
||||
v.app.app.SetFocus(v.input)
|
||||
return nil
|
||||
case '[', ']':
|
||||
if v.browsing {
|
||||
v.switchList(map[rune]int{'[': -1, ']': 1}[ev.Rune()])
|
||||
return nil
|
||||
}
|
||||
case 'r':
|
||||
if v.browsing {
|
||||
v.reloadList()
|
||||
return nil
|
||||
}
|
||||
case 'a':
|
||||
v.add(v.list.GetCurrentItem())
|
||||
return nil
|
||||
@@ -173,7 +203,7 @@ func (v *searchView) onSearchChanged(text string) {
|
||||
gen := v.invalidate()
|
||||
if len([]rune(text)) < minAutoSearch {
|
||||
if text == "" {
|
||||
v.setResults(nil)
|
||||
v.showList()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -199,7 +229,7 @@ func (v *searchView) search(text string) {
|
||||
}
|
||||
gen := v.invalidate()
|
||||
if text == "" {
|
||||
v.setResults(nil)
|
||||
v.showList()
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -232,19 +262,34 @@ func (v *searchView) run(ctx context.Context, gen int, term string) {
|
||||
})
|
||||
}
|
||||
|
||||
// setResults replaces the list contents (UI goroutine).
|
||||
// setResults replaces the list contents with search results (UI goroutine).
|
||||
func (v *searchView) setResults(results []result) {
|
||||
v.results = results
|
||||
v.results, v.notes, v.placeholder, v.browsing = results, nil, "", false
|
||||
v.list.SetTitle(" " + v.be.noun() + " ")
|
||||
v.render(0)
|
||||
}
|
||||
|
||||
// activate shows a Trakt list when the view opens with an empty search.
|
||||
func (v *searchView) activate() {
|
||||
if !v.browsing && len(v.results) == 0 && strings.TrimSpace(v.input.GetText()) == "" {
|
||||
v.showList()
|
||||
}
|
||||
}
|
||||
|
||||
// render rebuilds the list, selecting index sel.
|
||||
func (v *searchView) render(sel int) {
|
||||
v.list.Clear()
|
||||
for _, r := range v.results {
|
||||
v.list.AddItem(r.label(), "", 0, nil)
|
||||
for i, r := range v.results {
|
||||
label := r.label()
|
||||
if i < len(v.notes) && v.notes[i] != "" {
|
||||
label += " [gray]" + tview.Escape(v.notes[i]) + "[-]"
|
||||
}
|
||||
v.list.AddItem(label, "", 0, nil)
|
||||
}
|
||||
if len(v.results) == 0 {
|
||||
if v.placeholder != "" {
|
||||
v.list.AddItem("[gray]"+v.placeholder+"[-]", "", 0, nil)
|
||||
}
|
||||
v.details.Clear()
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user