From c6a39e5b75865a171628da87b93e3363010b7a75 Mon Sep 17 00:00:00 2001 From: ericwyn <16485562+Ericwyn@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:17:46 +0800 Subject: [PATCH] feat: tracks page supports selecting the sort order support view the most recently added songs --- backend/config.go | 2 + backend/mediaprovider/jellyfin/iterators.go | 6 +- backend/mediaprovider/mediaprovider.go | 4 +- .../mediaprovider/subsonic/trackiterator.go | 11 +++- ui/browsing/trackspage.go | 65 +++++++++++++++---- 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/backend/config.go b/backend/config.go index 4d409678..8804ce00 100644 --- a/backend/config.go +++ b/backend/config.go @@ -89,6 +89,7 @@ type PlaylistsPageConfig struct { type TracksPageConfig struct { TracklistColumns []string + SortOrder string } type NowPlayingPageConfig struct { @@ -203,6 +204,7 @@ func DefaultConfig(appVersionTag string) *Config { }, TracksPage: TracksPageConfig{ TracklistColumns: []string{"Album", "Time", "Plays"}, + SortOrder: string("Recently Added"), }, LocalPlayback: LocalPlaybackConfig{ // "auto" is the name to pass to MPV for autoselecting the output device diff --git a/backend/mediaprovider/jellyfin/iterators.go b/backend/mediaprovider/jellyfin/iterators.go index 05f87f88..974fe947 100644 --- a/backend/mediaprovider/jellyfin/iterators.go +++ b/backend/mediaprovider/jellyfin/iterators.go @@ -20,6 +20,10 @@ func (j *jellyfinMediaProvider) AlbumSortOrders() []string { } } +func (j *jellyfinMediaProvider) TrackSortOrders() []string { + return []string{} +} + func (j *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator { var jfSort jellyfin.Sort switch sortOrder { @@ -83,7 +87,7 @@ func (j *jellyfinMediaProvider) SearchAlbums(searchQuery string, filter mediapro return helpers.NewAlbumIterator(fetcher, filter, j.prefetchCoverCB) } -func (j *jellyfinMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator { +func (j *jellyfinMediaProvider) IterateTracks(sortOrder string, searchQuery string) mediaprovider.TrackIterator { var fetcher helpers.TrackFetchFn if searchQuery == "" { fetcher = func(offs, limit int) ([]*mediaprovider.Track, error) { diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index 256405ee..648561e2 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -212,7 +212,7 @@ type MediaProvider interface { IterateAlbums(sortOrder string, filter AlbumFilter) AlbumIterator - IterateTracks(searchQuery string) TrackIterator + IterateTracks(sortOrder string, searchQuery string) TrackIterator SearchAlbums(searchQuery string, filter AlbumFilter) AlbumIterator @@ -226,6 +226,8 @@ type MediaProvider interface { ArtistSortOrders() []string + TrackSortOrders() []string + IterateArtists(sortOrder string, filter ArtistFilter) ArtistIterator SearchArtists(searchQuery string, filter ArtistFilter) ArtistIterator diff --git a/backend/mediaprovider/subsonic/trackiterator.go b/backend/mediaprovider/subsonic/trackiterator.go index 4d1b44d0..b3b60d92 100644 --- a/backend/mediaprovider/subsonic/trackiterator.go +++ b/backend/mediaprovider/subsonic/trackiterator.go @@ -7,12 +7,19 @@ import ( "github.com/dweymouth/supersonic/backend/mediaprovider" ) -func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator { +func (s *subsonicMediaProvider) TrackSortOrders() []string { + return []string{ + mediaprovider.AlbumSortRecentlyAdded, + mediaprovider.AlbumSortArtistAZ, + } +} + +func (s *subsonicMediaProvider) IterateTracks(sortOrder string, searchQuery string) mediaprovider.TrackIterator { if searchQuery == "" { return &allTracksIterator{ s: s, albumIter: s.IterateAlbums( - mediaprovider.AlbumSortArtistAZ, + sortOrder, mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{}), ), } diff --git a/ui/browsing/trackspage.go b/ui/browsing/trackspage.go index 9e106bc2..54854a82 100644 --- a/ui/browsing/trackspage.go +++ b/ui/browsing/trackspage.go @@ -1,6 +1,11 @@ package browsing import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" @@ -8,12 +13,7 @@ import ( "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/widgets" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/lang" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/widget" + "slices" ) type TracksPage struct { @@ -24,6 +24,7 @@ type TracksPage struct { nowPlayingID string title *widget.RichText + sortOrder *sortOrderSelect searcher *widgets.SearchEntry tracklist *widgets.Tracklist loader widgets.TracklistLoader @@ -40,6 +41,7 @@ type tracksPageState struct { conf *backend.TracksPageConfig mp mediaprovider.MediaProvider im *backend.ImageManager + sorterId int canRate bool canShare bool } @@ -66,9 +68,8 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, } contr.ConnectTracklistActions(t.tracklist) - t.title = widget.NewRichTextWithText(lang.L("All Tracks")) - t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName - t.playRandom = widget.NewButtonWithIcon(lang.L("Play random"), theme.ShuffleIcon, t.playRandomSongs) + t.createTitleAndSort() + t.searcher = widgets.NewSearchEntry() t.searcher.PlaceHolder = lang.L("Search page") t.searcher.OnSearched = t.OnSearched @@ -77,10 +78,50 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, return t } +func (t *TracksPage) createTitleAndSort() { + t.title = widget.NewRichTextWithText(lang.L("All Tracks")) + sortOrders := t.mp.TrackSortOrders() + if len(sortOrders) > 1 { + sorts, selected := t.SortOrders() + t.sortOrder = NewSortOrderSelect(sorts, t.onSortOrderChanged) + t.sortOrder.SetSelectedIndex(selected) + t.sorterId = 0 + } + t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName + t.playRandom = widget.NewButtonWithIcon(lang.L("Play random"), theme.ShuffleIcon, t.playRandomSongs) +} + +func (t *TracksPage) SortOrders() ([]string, int) { + orders := t.mp.TrackSortOrders() + sortOrder := slices.Index(orders, t.conf.SortOrder) + if sortOrder < 0 { + sortOrder = 0 + } + + return util.LocalizeSlice(orders), sortOrder +} + +func (t *TracksPage) onSortOrderChanged(_ string) { + t.conf.SortOrder = t.mp.TrackSortOrders()[t.getSortOrderIdx()] + t.Reload() +} + +func (t *TracksPage) getSortOrderIdx() int { + if t.sortOrder != nil { + return t.sortOrder.SelectedIndex() + } + return 0 +} + func (t *TracksPage) createContainer() { playRandomVbox := container.NewVBox(layout.NewSpacer(), t.playRandom, layout.NewSpacer()) searchVbox := container.NewVBox(layout.NewSpacer(), t.searcher, layout.NewSpacer()) - topRow := container.NewHBox(t.title, playRandomVbox, layout.NewSpacer(), searchVbox) + var topRow *fyne.Container + if t.sortOrder != nil { + topRow = container.NewHBox(t.title, container.NewCenter(t.sortOrder), playRandomVbox, layout.NewSpacer(), searchVbox) + } else { + topRow = container.NewHBox(t.title, playRandomVbox, layout.NewSpacer(), searchVbox) + } t.container = container.New(&layout.CustomPaddedLayout{LeftPadding: 15, RightPadding: 15, TopPadding: 5, BottomPadding: 15}, container.NewBorder(topRow, nil, nil, nil, t.tracklist)) } @@ -91,7 +132,7 @@ func (t *TracksPage) Route() controller.Route { func (t *TracksPage) Reload() { t.tracklist.Clear() - iter := t.mp.IterateTracks("") + iter := t.mp.IterateTracks(t.mp.TrackSortOrders()[t.getSortOrderIdx()], "") // loads asynchronously t.loader = widgets.NewTracklistLoader(t.tracklist, iter) } @@ -155,7 +196,7 @@ func (t *TracksPage) doSearch(query string) { } else { t.searchTracklist.Clear() } - iter := t.mp.IterateTracks(query) + iter := t.mp.IterateTracks(t.mp.TrackSortOrders()[t.getSortOrderIdx()], query) t.searchLoader = widgets.NewTracklistLoader(t.searchTracklist, iter) t.container.Objects[0].(*fyne.Container).Objects[0] = t.searchTracklist t.Refresh()