From b21b1ce6ba0142d73ef733bedc667cedb2f3349e Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 20 Apr 2023 17:13:24 -0700 Subject: [PATCH] add TracklistLoader for infinite scrolling tracklist --- ui/widgets/tracklist.go | 18 ++++++++++ ui/widgets/tracklistloader.go | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 ui/widgets/tracklistloader.go diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 388784b..2868cbc 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -67,6 +67,7 @@ type Tracklist struct { OnShowAlbumPage func(albumID string) OnColumnVisibilityMenuShown func(*widget.PopUp) + OnTrackShown func(tracknum int) visibleColumns []bool @@ -113,6 +114,9 @@ func NewTracklist(tracks []*subsonic.Child) *Tracklist { i = itemID + 1 } tr.Update(t.TrackAt(itemID), itemID == t.nowPlayingIdx, i) + if t.OnTrackShown != nil { + t.OnTrackShown(itemID) + } }) t.container = container.NewBorder(t.hdr, nil, nil, nil, t.list) return t @@ -203,6 +207,20 @@ func (t *Tracklist) IncrementPlayCount(trackID string) { } } +// Remove all tracks from the tracklist. Thread-safe. +func (t *Tracklist) Clear() { + t.tracksMutex.Lock() + defer t.tracksMutex.Unlock() + t.Tracks = nil +} + +// Append more tracks to the tracklist. Thread-safe. +func (t *Tracklist) AppendTracks(trs []*subsonic.Child) { + t.tracksMutex.Lock() + defer t.tracksMutex.Unlock() + t.Tracks = append(t.Tracks, trs...) +} + func (t *Tracklist) SelectAll() { t.selectionMgr.SelectAll() t.list.Refresh() diff --git a/ui/widgets/tracklistloader.go b/ui/widgets/tracklistloader.go new file mode 100644 index 0000000..bbaa5eb --- /dev/null +++ b/ui/widgets/tracklistloader.go @@ -0,0 +1,67 @@ +package widgets + +import ( + "log" + "supersonic/backend" + + "github.com/dweymouth/go-subsonic/subsonic" +) + +// Component that manages lazily loading more tracks into a Tracklist +// as the user scrolls near the bottom. +type TracklistLoader struct { + tracklist *Tracklist + iter backend.TrackIterator + + trackBuffer []*subsonic.Child + fetching bool + done bool + len int + highestShown int +} + +func NewTracklistLoader(tracklist *Tracklist, iter backend.TrackIterator) TracklistLoader { + t := TracklistLoader{ + tracklist: tracklist, + iter: iter, + } + t.tracklist.OnTrackShown = t.onTrackShown + t.fetching = true + go t.loadMoreTracks(25) + return t +} + +func (t *TracklistLoader) onTrackShown(tracknum int) { + if tracknum > t.highestShown { + t.highestShown = tracknum + } + if t.highestShown >= t.len-25 && !t.fetching && !t.done { + t.fetching = true + go t.loadMoreTracks(25) + } +} + +func (t *TracklistLoader) loadMoreTracks(num int) { + // repeat fetch task as long as user has scrolled near bottom + for !t.done && t.highestShown >= t.len-25 { + log.Println("fetching more tracks") + if t.trackBuffer == nil { + t.trackBuffer = make([]*subsonic.Child, 0, num) + } + t.trackBuffer = t.trackBuffer[:0] + for i := 0; i < num; i++ { + tr := t.iter.Next() + if tr == nil { + t.done = true + t.trackBuffer = nil + break + } + t.trackBuffer = append(t.trackBuffer, tr) + } + t.tracklist.AppendTracks(t.trackBuffer) + t.tracklist.Refresh() + log.Printf("appended %d tracks", len(t.trackBuffer)) + t.len += len(t.trackBuffer) + } + t.fetching = false +}