add artist discography page
This commit is contained in:
+44
-23
@@ -22,12 +22,24 @@ type AlbumGrid struct {
|
||||
fetching bool
|
||||
done bool
|
||||
|
||||
imageFetcher ImageFetcher
|
||||
OnPlayAlbum func(string)
|
||||
imageFetcher ImageFetcher
|
||||
OnPlayAlbum func(string)
|
||||
OnShowArtistPage func(string)
|
||||
}
|
||||
|
||||
var _ fyne.Widget = (*AlbumGrid)(nil)
|
||||
|
||||
func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher) *AlbumGrid {
|
||||
ag := &AlbumGrid{
|
||||
albums: albums,
|
||||
done: true,
|
||||
imageFetcher: fetch,
|
||||
}
|
||||
ag.ExtendBaseWidget(ag)
|
||||
ag.createGridWrapList()
|
||||
return ag
|
||||
}
|
||||
|
||||
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher) *AlbumGrid {
|
||||
ag := &AlbumGrid{
|
||||
iter: iter,
|
||||
@@ -35,27 +47,7 @@ func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher) *AlbumGrid {
|
||||
}
|
||||
ag.ExtendBaseWidget(ag)
|
||||
|
||||
g := widget.NewGridWrapList(
|
||||
func() int {
|
||||
return len(ag.albums)
|
||||
},
|
||||
// create func
|
||||
func() fyne.CanvasObject {
|
||||
ac := widgets.NewAlbumCard()
|
||||
ac.OnPlay = func() {
|
||||
if ag.OnPlayAlbum != nil {
|
||||
ag.OnPlayAlbum(ac.AlbumID())
|
||||
}
|
||||
}
|
||||
return ac
|
||||
},
|
||||
// update func
|
||||
func(itemID int, obj fyne.CanvasObject) {
|
||||
ac := obj.(*widgets.AlbumCard)
|
||||
ag.doUpdateAlbumCard(itemID, ac)
|
||||
},
|
||||
)
|
||||
ag.grid = g
|
||||
ag.createGridWrapList()
|
||||
|
||||
// fetch initial albums
|
||||
ag.fetchMoreAlbums(36)
|
||||
@@ -75,6 +67,35 @@ func (ag *AlbumGrid) Reset(iter backend.AlbumIterator) {
|
||||
ag.fetchMoreAlbums(36)
|
||||
}
|
||||
|
||||
func (ag *AlbumGrid) createGridWrapList() {
|
||||
g := widget.NewGridWrapList(
|
||||
func() int {
|
||||
return len(ag.albums)
|
||||
},
|
||||
// create func
|
||||
func() fyne.CanvasObject {
|
||||
ac := widgets.NewAlbumCard()
|
||||
ac.OnPlay = func() {
|
||||
if ag.OnPlayAlbum != nil {
|
||||
ag.OnPlayAlbum(ac.AlbumID())
|
||||
}
|
||||
}
|
||||
ac.OnShowArtistPage = func() {
|
||||
if ag.OnShowArtistPage != nil {
|
||||
ag.OnShowArtistPage(ac.ArtistID())
|
||||
}
|
||||
}
|
||||
return ac
|
||||
},
|
||||
// update func
|
||||
func(itemID int, obj fyne.CanvasObject) {
|
||||
ac := obj.(*widgets.AlbumCard)
|
||||
ag.doUpdateAlbumCard(itemID, ac)
|
||||
},
|
||||
)
|
||||
ag.grid = g
|
||||
}
|
||||
|
||||
func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *widgets.AlbumCard) {
|
||||
album := ag.albums[albumIdx]
|
||||
if ac.PrevAlbumID == album.ID {
|
||||
|
||||
+15
-4
@@ -18,6 +18,7 @@ type AlbumsPage struct {
|
||||
|
||||
im *backend.ImageManager
|
||||
lm *backend.LibraryManager
|
||||
nav func(Route)
|
||||
grid *AlbumGrid
|
||||
searchGrid *AlbumGrid
|
||||
searchText string
|
||||
@@ -48,10 +49,11 @@ func (s *selectWidget) MinSize() fyne.Size {
|
||||
return fyne.NewSize(170, s.height)
|
||||
}
|
||||
|
||||
func NewAlbumsPage(title string, lm *backend.LibraryManager, im *backend.ImageManager) *AlbumsPage {
|
||||
func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumsPage {
|
||||
a := &AlbumsPage{
|
||||
lm: lm,
|
||||
im: im,
|
||||
lm: lm,
|
||||
im: im,
|
||||
nav: nav,
|
||||
}
|
||||
a.ExtendBaseWidget(a)
|
||||
|
||||
@@ -60,11 +62,12 @@ func NewAlbumsPage(title string, lm *backend.LibraryManager, im *backend.ImageMa
|
||||
SizeName: theme.SizeNameHeadingText,
|
||||
}
|
||||
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
|
||||
a.sortOrder.Selected = a.sortOrder.Options[0]
|
||||
a.sortOrder.Selected = sortOrder
|
||||
a.sortOrder.OnChanged = a.onSortOrderChanged
|
||||
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
|
||||
a.grid = NewAlbumGrid(lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)), im.GetAlbumThumbnail)
|
||||
a.grid.OnPlayAlbum = a.onPlayAlbum
|
||||
a.grid.OnShowArtistPage = a.onShowArtistPage
|
||||
a.container = container.NewBorder(
|
||||
container.NewHBox(widgets.NewHSpace(15), a.titleDisp, sortVbox),
|
||||
nil,
|
||||
@@ -95,12 +98,20 @@ func (a *AlbumsPage) OnSearched(query string) {
|
||||
a.Refresh()
|
||||
}
|
||||
|
||||
func (a *AlbumsPage) SetPlayAlbumCallback(cb func(string)) {
|
||||
a.OnPlayAlbum = cb
|
||||
}
|
||||
|
||||
func (a *AlbumsPage) onPlayAlbum(albumID string) {
|
||||
if a.OnPlayAlbum != nil {
|
||||
a.OnPlayAlbum(albumID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlbumsPage) onShowArtistPage(artistID string) {
|
||||
a.nav(ArtistRoute(artistID))
|
||||
}
|
||||
|
||||
func (a *AlbumsPage) onSortOrderChanged(order string) {
|
||||
a.grid.Reset(a.lm.AlbumsIter(backend.AlbumSortOrder(order)))
|
||||
if a.searchText == "" {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"log"
|
||||
"supersonic/backend"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*ArtistPage)(nil)
|
||||
|
||||
type ArtistPage struct {
|
||||
widget.BaseWidget
|
||||
|
||||
artistID string
|
||||
im *backend.ImageManager
|
||||
sm *backend.ServerManager
|
||||
grid *AlbumGrid
|
||||
titleDisp *widget.RichText
|
||||
container *fyne.Container
|
||||
OnPlayAlbum func(string)
|
||||
}
|
||||
|
||||
func NewArtistPage(artistID string, sm *backend.ServerManager, im *backend.ImageManager) *ArtistPage {
|
||||
a := &ArtistPage{
|
||||
artistID: artistID,
|
||||
sm: sm,
|
||||
im: im,
|
||||
}
|
||||
a.ExtendBaseWidget(a)
|
||||
a.titleDisp = widget.NewRichTextWithText("Artist")
|
||||
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
|
||||
SizeName: theme.SizeNameHeadingText,
|
||||
}
|
||||
a.container = container.NewBorder(a.titleDisp, nil, nil, nil, layout.NewSpacer())
|
||||
a.loadAsync()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArtistPage) SetPlayAlbumCallback(cb func(string)) {
|
||||
a.OnPlayAlbum = cb
|
||||
}
|
||||
|
||||
func (a *ArtistPage) onPlayAlbum(albumID string) {
|
||||
if a.OnPlayAlbum != nil {
|
||||
a.OnPlayAlbum(albumID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ArtistPage) loadAsync() {
|
||||
go func() {
|
||||
artist, err := a.sm.Server.GetArtist(a.artistID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get artist: %s", err.Error())
|
||||
return
|
||||
}
|
||||
a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name
|
||||
a.titleDisp.Refresh()
|
||||
ag := NewFixedAlbumGrid(artist.Album, a.im.GetAlbumThumbnail)
|
||||
ag.OnPlayAlbum = a.onPlayAlbum
|
||||
a.container.Objects[0] = ag
|
||||
a.container.Refresh()
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
|
||||
a.ExtendBaseWidget(a)
|
||||
return widget.NewSimpleRenderer(a.container)
|
||||
}
|
||||
+16
-2
@@ -1,6 +1,7 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"supersonic/backend"
|
||||
"supersonic/ui/widgets"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -18,9 +19,15 @@ type Searchable interface {
|
||||
OnSearched(string)
|
||||
}
|
||||
|
||||
type CanPlayAlbum interface {
|
||||
SetPlayAlbumCallback(func(albumID string))
|
||||
}
|
||||
|
||||
type BrowsingPane struct {
|
||||
widget.BaseWidget
|
||||
|
||||
app *backend.App
|
||||
|
||||
searchBar *widgets.SearchEntry
|
||||
pendingSearchLock sync.Mutex
|
||||
pendingSearch bool
|
||||
@@ -34,8 +41,8 @@ type blankPage struct {
|
||||
widget.Separator
|
||||
}
|
||||
|
||||
func NewBrowsingPane() *BrowsingPane {
|
||||
b := &BrowsingPane{}
|
||||
func NewBrowsingPane(app *backend.App) *BrowsingPane {
|
||||
b := &BrowsingPane{app: app}
|
||||
b.ExtendBaseWidget(b)
|
||||
b.searchBar = widgets.NewSearchEntry()
|
||||
b.searchBar.OnTextChanged = b.onSearchTextChanged
|
||||
@@ -48,6 +55,13 @@ func NewBrowsingPane() *BrowsingPane {
|
||||
|
||||
func (b *BrowsingPane) SetPage(p Page) {
|
||||
b.curPage = p
|
||||
if pa, ok := p.(CanPlayAlbum); ok {
|
||||
pa.SetPlayAlbumCallback(func(albumID string) {
|
||||
_ = b.app.PlaybackManager.PlayAlbum(albumID)
|
||||
})
|
||||
}
|
||||
_, s := p.(Searchable)
|
||||
b.searchBar.Hidden = !s
|
||||
b.container.Objects[0] = p
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
+4
-6
@@ -13,6 +13,7 @@ import (
|
||||
type MainWindow struct {
|
||||
Window fyne.Window
|
||||
|
||||
Router Router
|
||||
BrowsingPane *BrowsingPane
|
||||
BottomPanel *BottomPanel
|
||||
|
||||
@@ -22,9 +23,10 @@ type MainWindow struct {
|
||||
func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindow {
|
||||
m := MainWindow{
|
||||
Window: fyneApp.NewWindow(appName),
|
||||
BrowsingPane: NewBrowsingPane(),
|
||||
BrowsingPane: NewBrowsingPane(app),
|
||||
BottomPanel: NewBottomPanel(app.Player),
|
||||
}
|
||||
m.Router = NewRouter(app, m.BrowsingPane)
|
||||
m.BottomPanel.SetPlaybackManager(app.PlaybackManager)
|
||||
m.BottomPanel.ImageManager = app.ImageManager
|
||||
m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane)
|
||||
@@ -38,11 +40,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo
|
||||
m.Window.SetTitle(song.Title)
|
||||
})
|
||||
app.ServerManager.OnServerConnected(func() {
|
||||
ap := NewAlbumsPage("Albums", app.LibraryManager, app.ImageManager)
|
||||
ap.OnPlayAlbum = func(albumID string) {
|
||||
_ = app.PlaybackManager.PlayAlbum(albumID)
|
||||
}
|
||||
m.BrowsingPane.SetPage(ap)
|
||||
m.Router.OpenRoute(AlbumsRoute(backend.AlbumSortFrequentlyPlayed))
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package ui
|
||||
|
||||
import "supersonic/backend"
|
||||
|
||||
type PageName int
|
||||
|
||||
const (
|
||||
Blank PageName = iota
|
||||
Album
|
||||
Albums
|
||||
Artist
|
||||
Artists
|
||||
Playlist
|
||||
Playlists
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
Page PageName
|
||||
Arg string
|
||||
}
|
||||
|
||||
func AlbumsRoute(sortOrder backend.AlbumSortOrder) Route {
|
||||
return Route{Page: Albums, Arg: string(sortOrder)}
|
||||
}
|
||||
|
||||
func ArtistRoute(artistID string) Route {
|
||||
return Route{Page: Artist, Arg: artistID}
|
||||
}
|
||||
|
||||
type NavigationHandler interface {
|
||||
SetPage(Page)
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
App *backend.App
|
||||
Nav NavigationHandler
|
||||
}
|
||||
|
||||
func NewRouter(app *backend.App, nav NavigationHandler) Router {
|
||||
return Router{
|
||||
App: app,
|
||||
Nav: nav,
|
||||
}
|
||||
}
|
||||
|
||||
func (r Router) CreatePage(rte Route) Page {
|
||||
switch rte.Page {
|
||||
case Albums:
|
||||
return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
||||
case Artist:
|
||||
return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) OpenRoute(rte Route) {
|
||||
r.Nav.SetPage(r.CreatePage(rte))
|
||||
}
|
||||
+16
-4
@@ -62,8 +62,9 @@ type AlbumCard struct {
|
||||
widget.BaseWidget
|
||||
|
||||
albumID string
|
||||
artistID string
|
||||
title *widget.Label
|
||||
artist *widget.Label
|
||||
artist *CustomHyperlink
|
||||
container *fyne.Container
|
||||
|
||||
// updated by AlbumGrid
|
||||
@@ -73,7 +74,9 @@ type AlbumCard struct {
|
||||
PrevAlbumID string
|
||||
ImgLoadCancel context.CancelFunc
|
||||
|
||||
OnPlay func()
|
||||
OnPlay func()
|
||||
OnShowAlbumPage func()
|
||||
OnShowArtistPage func()
|
||||
}
|
||||
|
||||
func (a *AlbumCard) MouseIn(*desktop.MouseEvent) {}
|
||||
@@ -85,7 +88,7 @@ func (a *AlbumCard) MouseMoved(*desktop.MouseEvent) {}
|
||||
func NewAlbumCard() *AlbumCard {
|
||||
a := &AlbumCard{
|
||||
title: widget.NewLabel(""),
|
||||
artist: widget.NewLabel(""),
|
||||
artist: NewCustomHyperlink(),
|
||||
Cover: newAlbumCover(),
|
||||
}
|
||||
a.ExtendBaseWidget(a)
|
||||
@@ -94,8 +97,12 @@ func NewAlbumCard() *AlbumCard {
|
||||
a.OnPlay()
|
||||
}
|
||||
}
|
||||
a.artist.OnTapped = func() {
|
||||
if a.OnShowArtistPage != nil {
|
||||
a.OnShowArtistPage()
|
||||
}
|
||||
}
|
||||
a.title.Wrapping = fyne.TextTruncate
|
||||
a.artist.Wrapping = fyne.TextTruncate
|
||||
a.title.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
a.createContainer()
|
||||
@@ -113,12 +120,17 @@ func (a *AlbumCard) Update(al *subsonic.AlbumID3) {
|
||||
a.title.SetText(al.Name)
|
||||
a.artist.SetText(al.Artist)
|
||||
a.albumID = al.ID
|
||||
a.artistID = al.ArtistID
|
||||
}
|
||||
|
||||
func (a *AlbumCard) AlbumID() string {
|
||||
return a.albumID
|
||||
}
|
||||
|
||||
func (a *AlbumCard) ArtistID() string {
|
||||
return a.artistID
|
||||
}
|
||||
|
||||
func (a *AlbumCard) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(a.container)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ type CustomHyperlink struct {
|
||||
widget.BaseWidget
|
||||
h *hyperlinkWrapper
|
||||
|
||||
OnTapped func()
|
||||
|
||||
container *fyne.Container
|
||||
minSize fyne.Size
|
||||
}
|
||||
@@ -52,6 +54,11 @@ func NewCustomHyperlink() *CustomHyperlink {
|
||||
c := &CustomHyperlink{
|
||||
h: newHyperlinkWrapper(200),
|
||||
}
|
||||
c.h.h.OnTapped = func() {
|
||||
if c.OnTapped != nil {
|
||||
c.OnTapped()
|
||||
}
|
||||
}
|
||||
c.ExtendBaseWidget(c)
|
||||
c.container = container.NewHBox(c.h, layout.NewSpacer())
|
||||
return c
|
||||
|
||||
Reference in New Issue
Block a user