Fix #665: Add option to play albums in order on Albums page

This commit is contained in:
Drew Weymouth
2025-08-10 15:06:27 -07:00
parent 43670538b9
commit a317b382ff
5 changed files with 100 additions and 4 deletions
+2 -1
View File
@@ -70,7 +70,8 @@ type AlbumPageConfig struct {
type AlbumsPageConfig struct { type AlbumsPageConfig struct {
SortOrder string // only relevant for Albums page SortOrder string // only relevant for Albums page
ShowYears bool ShowYears bool
ShuffleMode string // only relevant for genre page ShuffleMode string // only relevant for Genre page
PlayInOrder bool // only relevant for Albums page
} }
type ArtistPageConfig struct { type ArtistPageConfig struct {
+4
View File
@@ -108,6 +108,7 @@
"Home Page": "Home Page", "Home Page": "Home Page",
"hr": "hr", "hr": "hr",
"hrs": "hrs", "hrs": "hrs",
"In order": "In order",
"Internet Radio Stations": "Internet Radio Stations", "Internet Radio Stations": "Internet Radio Stations",
"Interview": "Interview", "Interview": "Interview",
"Is favorite": "Is favorite", "Is favorite": "Is favorite",
@@ -202,6 +203,7 @@
"Show play queue": "Show play queue", "Show play queue": "Show play queue",
"Show year in album grid cards": "Show year in album grid cards", "Show year in album grid cards": "Show year in album grid cards",
"Shuffle": "Shuffle", "Shuffle": "Shuffle",
"Shuffled": "Shuffled",
"Shuffle albums": "Shuffle albums", "Shuffle albums": "Shuffle albums",
"Shuffle tracks": "Shuffle tracks", "Shuffle tracks": "Shuffle tracks",
"Similar artists": "Similar artists", "Similar artists": "Similar artists",
@@ -240,6 +242,7 @@
"tracks": "tracks", "tracks": "tracks",
"Transcode to": "Transcode to", "Transcode to": "Transcode to",
"UI Scaling": "UI Scaling", "UI Scaling": "UI Scaling",
"Unable to play albums": "Unable to play albums",
"Unable to play artist radio": "Unable to play artist radio", "Unable to play artist radio": "Unable to play artist radio",
"Unable to play random tracks": "Unable to play random tracks", "Unable to play random tracks": "Unable to play random tracks",
"URL": "URL", "URL": "URL",
@@ -263,6 +266,7 @@
"Set favorite": "Set favorite", "Set favorite": "Set favorite",
"Unset favorite": "Unset favorite", "Unset favorite": "Unset favorite",
"Remove from queue": "Remove from queue", "Remove from queue": "Remove from queue",
"Play albums": "Play albums",
"Play song radio": "Play song radio", "Play song radio": "Play song radio",
"to": "to", "to": "to",
"by": "by", "by": "by",
+71 -3
View File
@@ -6,7 +6,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/theme"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/controller"
@@ -22,6 +22,9 @@ type albumsPageAdapter struct {
pm *backend.PlaybackManager pm *backend.PlaybackManager
filter mediaprovider.AlbumFilter filter mediaprovider.AlbumFilter
filterBtn *widgets.AlbumFilterButton filterBtn *widgets.AlbumFilterButton
// dependency injected from the GridViewPage
itemsFn func() []widgets.GridViewItemModel
} }
func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) Page { func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) Page {
@@ -29,6 +32,12 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *
return NewGridViewPage(adapter, pool, mp, im) return NewGridViewPage(adapter, pool, mp, im)
} }
var _ GridViewPageAdapterGetItems = (*albumsPageAdapter)(nil)
func (a *albumsPageAdapter) SetItemsFunc(f func() []widgets.GridViewItemModel) {
a.itemsFn = f
}
func (a *albumsPageAdapter) Title() string { return lang.L("Albums") } func (a *albumsPageAdapter) Title() string { return lang.L("Albums") }
func (a *albumsPageAdapter) Filter() mediaprovider.AlbumFilter { func (a *albumsPageAdapter) Filter() mediaprovider.AlbumFilter {
@@ -66,7 +75,7 @@ func (a *albumsPageAdapter) SaveSortOrder(orderIdx int) {
} }
func (a *albumsPageAdapter) ActionButton() fyne.CanvasObject { func (a *albumsPageAdapter) ActionButton() fyne.CanvasObject {
fn := func() { shuffleAlbumsFn := func() {
go func() { go func() {
if err := a.pm.PlayRandomAlbums(""); err != nil { if err := a.pm.PlayRandomAlbums(""); err != nil {
log.Printf("error playing random albums: %v", err) log.Printf("error playing random albums: %v", err)
@@ -77,7 +86,66 @@ func (a *albumsPageAdapter) ActionButton() fyne.CanvasObject {
}() }()
} }
return widget.NewButtonWithIcon(lang.L("Play random"), myTheme.ShuffleIcon, fn) playAlbumsFn := func() {
if a.itemsFn == nil {
a.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play albums"))
}
go func() {
for i, item := range a.itemsFn() {
if i >= 20 {
break // don't load more than first 20 albums
}
if i == 0 {
a.pm.LoadAlbum(item.ID, backend.Replace, false)
a.pm.PlayFromBeginning()
} else {
a.pm.LoadAlbum(item.ID, backend.Append, false)
}
}
}()
}
var btn *widgets.OptionButton
var inOrder, shuffled *fyne.MenuItem
inOrder = fyne.NewMenuItem(lang.L("In order"), func() {
shuffled.Checked = false
inOrder.Checked = true
a.cfg.PlayInOrder = true
btn.Text = lang.L("Play albums")
btn.Icon = theme.MediaPlayIcon()
btn.Refresh()
})
inOrder.Icon = myTheme.AlbumIcon
shuffled = fyne.NewMenuItem(lang.L("Shuffled"), func() {
inOrder.Checked = false
shuffled.Checked = true
a.cfg.PlayInOrder = false
btn.Text = lang.L("Shuffle albums")
btn.Icon = myTheme.ShuffleIcon
btn.Refresh()
})
shuffled.Icon = myTheme.ShuffleIcon
shuffled.Checked = !a.cfg.PlayInOrder
inOrder.Checked = a.cfg.PlayInOrder
menu := fyne.NewMenu("", inOrder, shuffled)
icon := myTheme.ShuffleIcon
textKey := "Shuffle albums"
if a.cfg.PlayInOrder {
textKey = "Play albums"
icon = theme.MediaPlayIcon()
}
btn = widgets.NewOptionButton(lang.L(textKey), menu, func() {
if a.cfg.PlayInOrder {
playAlbumsFn()
} else {
shuffleAlbumsFn()
}
})
btn.Icon = icon
return btn
} }
func (a *albumsPageAdapter) Iter(sortOrderIdx int, filter mediaprovider.AlbumFilter) widgets.GridViewIterator { func (a *albumsPageAdapter) Iter(sortOrderIdx int, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
+13
View File
@@ -76,6 +76,13 @@ type GridViewPageAdapter[M, F any] interface {
RefreshGrid(*widgets.GridView) RefreshGrid(*widgets.GridView)
} }
type GridViewPageAdapterGetItems interface {
// Optionally allows the GridViewPage to inject a function that
// can be used to retrieve the GridViewItemModels for the
// items currently loaded into the GridView.
SetItemsFunc(func() []widgets.GridViewItemModel)
}
type SortableGridViewPageAdapter interface { type SortableGridViewPageAdapter interface {
// Returns the list of sort orders // Returns the list of sort orders
// and the index of the initially selected sort order // and the index of the initially selected sort order
@@ -113,6 +120,12 @@ func NewGridViewPage[M, F any](
} }
gp.grid.DisableSharing = !canShare gp.grid.DisableSharing = !canShare
adapter.InitGrid(gp.grid) adapter.InitGrid(gp.grid)
// If adapter supports SetItemsFunc, call it to inject the items dependency
if plfSetter, ok := adapter.(GridViewPageAdapterGetItems); ok {
plfSetter.SetItemsFunc(gp.grid.Items)
}
gp.createSearchAndFilter() gp.createSearchAndFilter()
gp.createContainer() gp.createContainer()
return gp return gp
+10
View File
@@ -198,6 +198,16 @@ func NewGridView(iter GridViewIterator, fetch util.ImageFetcher, placeholder fyn
return g return g
} }
// Items returns the items that are currently loaded into the GridView state.
func (g *GridView) Items() []GridViewItemModel {
g.stateMutex.Lock()
defer g.stateMutex.Unlock()
items := make([]GridViewItemModel, len(g.items))
copy(items, g.items)
return items
}
func (g *GridView) SaveToState() *GridViewState { func (g *GridView) SaveToState() *GridViewState {
g.stateMutex.RLock() g.stateMutex.RLock()
s := g.GridViewState s := g.GridViewState