From 87b4762ae1a522960d99351146c4743fb81b62d5 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 31 Dec 2022 16:30:38 -0800 Subject: [PATCH 1/5] add custom hyperlink --- ui/widgets/customhyperlink.go | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 ui/widgets/customhyperlink.go diff --git a/ui/widgets/customhyperlink.go b/ui/widgets/customhyperlink.go new file mode 100644 index 0000000..6ec42d0 --- /dev/null +++ b/ui/widgets/customhyperlink.go @@ -0,0 +1,73 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" +) + +type hyperlinkWrapper struct { + widget.BaseWidget + + h *widget.Hyperlink + l *widget.Label + maxWidth float32 +} + +func newHyperlinkWrapper(maxWidth float32) *hyperlinkWrapper { + h := &hyperlinkWrapper{ + h: widget.NewHyperlink("", nil), + l: widget.NewLabel(""), + maxWidth: maxWidth, + } + h.h.Wrapping = fyne.TextTruncate + h.ExtendBaseWidget(h) + return h +} + +func (h *hyperlinkWrapper) MinSize() fyne.Size { + w := fyne.Min(h.maxWidth, h.l.MinSize().Width) + return fyne.NewSize(w, h.h.MinSize().Height) +} + +func (h *hyperlinkWrapper) SetText(text string) { + h.h.SetText(text) + h.l.SetText(text) +} + +func (h *hyperlinkWrapper) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(h.h) +} + +type CustomHyperlink struct { + widget.BaseWidget + h *hyperlinkWrapper + + container *fyne.Container + minSize fyne.Size +} + +func NewCustomHyperlink() *CustomHyperlink { + c := &CustomHyperlink{ + h: newHyperlinkWrapper(200), + } + c.ExtendBaseWidget(c) + c.container = container.NewHBox(c.h, layout.NewSpacer()) + return c +} + +func (c *CustomHyperlink) SetText(text string) { + s := widget.NewLabel(text).MinSize() + c.h.SetText(text) + c.minSize = fyne.NewSize(fyne.Min(c.Size().Width, s.Width), s.Height) + c.Refresh() +} + +func (c *CustomHyperlink) Refresh() { + c.container.Refresh() +} + +func (c *CustomHyperlink) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(c.container) +} From 0ff1b6bafda1c54ce2ee4feabd624ba928c18d94 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 31 Dec 2022 21:13:24 -0800 Subject: [PATCH 2/5] add artist discography page --- ui/albumgrid.go | 67 +++++++++++++++++++++----------- ui/albumspage.go | 19 +++++++-- ui/artistpage.go | 73 +++++++++++++++++++++++++++++++++++ ui/browsingpane.go | 18 ++++++++- ui/mainwindow.go | 10 ++--- ui/router.go | 58 ++++++++++++++++++++++++++++ ui/widgets/albumcard.go | 20 ++++++++-- ui/widgets/customhyperlink.go | 7 ++++ 8 files changed, 233 insertions(+), 39 deletions(-) create mode 100644 ui/artistpage.go create mode 100644 ui/router.go diff --git a/ui/albumgrid.go b/ui/albumgrid.go index 4b5de41..6a1f3ea 100644 --- a/ui/albumgrid.go +++ b/ui/albumgrid.go @@ -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 { diff --git a/ui/albumspage.go b/ui/albumspage.go index e891459..a36cbb6 100644 --- a/ui/albumspage.go +++ b/ui/albumspage.go @@ -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 == "" { diff --git a/ui/artistpage.go b/ui/artistpage.go new file mode 100644 index 0000000..80232c7 --- /dev/null +++ b/ui/artistpage.go @@ -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) +} diff --git a/ui/browsingpane.go b/ui/browsingpane.go index 110a97e..352bceb 100644 --- a/ui/browsingpane.go +++ b/ui/browsingpane.go @@ -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() } diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 686637f..def8fc5 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -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 } diff --git a/ui/router.go b/ui/router.go new file mode 100644 index 0000000..0ea6280 --- /dev/null +++ b/ui/router.go @@ -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)) +} diff --git a/ui/widgets/albumcard.go b/ui/widgets/albumcard.go index 354dce8..7a49536 100644 --- a/ui/widgets/albumcard.go +++ b/ui/widgets/albumcard.go @@ -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) } diff --git a/ui/widgets/customhyperlink.go b/ui/widgets/customhyperlink.go index 6ec42d0..99367fe 100644 --- a/ui/widgets/customhyperlink.go +++ b/ui/widgets/customhyperlink.go @@ -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 From e27cb1a0486badd2a1eb82d63c882ef9297159dc Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 1 Jan 2023 09:34:51 -0800 Subject: [PATCH 3/5] add history and navigation controls --- ui/browsingpane.go | 38 ++++++++++++++++++++++++++++++++++++-- ui/mainwindow.go | 2 +- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ui/browsingpane.go b/ui/browsingpane.go index 352bceb..f97883c 100644 --- a/ui/browsingpane.go +++ b/ui/browsingpane.go @@ -8,6 +8,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -28,11 +29,17 @@ type BrowsingPane struct { app *backend.App + curPage Page + + forward *widget.Button + back *widget.Button + history []Page + historyIdx int + searchBar *widgets.SearchEntry pendingSearchLock sync.Mutex pendingSearch bool searchGoroutine bool - curPage Page container *fyne.Container } @@ -46,14 +53,21 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane { b.ExtendBaseWidget(b) b.searchBar = widgets.NewSearchEntry() b.searchBar.OnTextChanged = b.onSearchTextChanged + b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack) + b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) b.curPage = &blankPage{} b.container = container.NewBorder( - container.NewHBox(widgets.NewHSpace(15), b.searchBar), + container.NewHBox(b.back, b.forward, b.searchBar), nil, nil, nil, b.curPage) return b } func (b *BrowsingPane) SetPage(p Page) { + b.addPageToHistory(p) + b.doSetPage(p) +} + +func (b *BrowsingPane) doSetPage(p Page) { b.curPage = p if pa, ok := p.(CanPlayAlbum); ok { pa.SetPlayAlbumCallback(func(albumID string) { @@ -66,6 +80,26 @@ func (b *BrowsingPane) SetPage(p Page) { b.Refresh() } +func (b *BrowsingPane) addPageToHistory(p Page) { + b.history = b.history[:b.historyIdx] + b.history = append(b.history, p) + b.historyIdx++ +} + +func (b *BrowsingPane) GoBack() { + if b.historyIdx > 1 { + b.historyIdx -= 1 + b.doSetPage(b.history[b.historyIdx-1]) + } +} + +func (b *BrowsingPane) GoForward() { + if b.historyIdx < len(b.history) { + b.historyIdx++ + b.doSetPage(b.history[b.historyIdx-1]) + } +} + func (b *BrowsingPane) onSearchTextChanged(text string) { if text == "" { b.sendSearch("") diff --git a/ui/mainwindow.go b/ui/mainwindow.go index def8fc5..81f79e4 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -40,7 +40,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo m.Window.SetTitle(song.Title) }) app.ServerManager.OnServerConnected(func() { - m.Router.OpenRoute(AlbumsRoute(backend.AlbumSortFrequentlyPlayed)) + m.Router.OpenRoute(AlbumsRoute(backend.AlbumSortRecentlyAdded)) }) return m } From 12d66680cf9b69b5aa4df48ef52e92ed3f33db56 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 1 Jan 2023 10:12:30 -0800 Subject: [PATCH 4/5] log out config and cache dirs on startup --- backend/app.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/app.go b/backend/app.go index 8f56b1e..d4f2b0b 100644 --- a/backend/app.go +++ b/backend/app.go @@ -37,6 +37,10 @@ func StartupApp() (*App, error) { a := &App{} a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) + log.Printf("Starting %s...", AppName) + log.Printf("Using config dir: %s", configdir.LocalConfig(AppName)) + log.Printf("Using cache dir: %s", configdir.LocalCache(AppName)) + a.readConfig() if err := a.initMPV(); err != nil { From 97458fa74305c21e322a8c38a9450f94289d0701 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 1 Jan 2023 10:12:58 -0800 Subject: [PATCH 5/5] show album year on artist page instead of artist name --- ui/albumgrid.go | 11 ++++++++--- ui/albumspage.go | 6 ++++-- ui/artistpage.go | 2 +- ui/widgets/albumcard.go | 23 +++++++++++++++++------ 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/ui/albumgrid.go b/ui/albumgrid.go index 6a1f3ea..6740c26 100644 --- a/ui/albumgrid.go +++ b/ui/albumgrid.go @@ -3,6 +3,7 @@ package ui import ( "context" "image" + "log" "supersonic/backend" "supersonic/ui/widgets" @@ -21,6 +22,7 @@ type AlbumGrid struct { iter backend.AlbumIterator fetching bool done bool + showYear bool imageFetcher ImageFetcher OnPlayAlbum func(string) @@ -29,18 +31,19 @@ type AlbumGrid struct { var _ fyne.Widget = (*AlbumGrid)(nil) -func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher) *AlbumGrid { +func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *AlbumGrid { ag := &AlbumGrid{ albums: albums, done: true, imageFetcher: fetch, + showYear: showYear, } ag.ExtendBaseWidget(ag) ag.createGridWrapList() return ag } -func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher) *AlbumGrid { +func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher, showYear bool) *AlbumGrid { ag := &AlbumGrid{ iter: iter, imageFetcher: fetch, @@ -74,7 +77,7 @@ func (ag *AlbumGrid) createGridWrapList() { }, // create func func() fyne.CanvasObject { - ac := widgets.NewAlbumCard() + ac := widgets.NewAlbumCard(ag.showYear) ac.OnPlay = func() { if ag.OnPlayAlbum != nil { ag.OnPlayAlbum(ac.AlbumID()) @@ -120,6 +123,8 @@ func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *widgets.AlbumCard) { if err == nil { ac.Cover.SetImage(i) ac.Cover.Refresh() + } else { + log.Printf("error fetching image: %s", err.Error()) } } }(ctx) diff --git a/ui/albumspage.go b/ui/albumspage.go index a36cbb6..3d4f3ae 100644 --- a/ui/albumspage.go +++ b/ui/albumspage.go @@ -65,7 +65,8 @@ func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, i 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) + iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) + a.grid = NewAlbumGrid(iter, im.GetAlbumThumbnail, false /*showYear*/) a.grid.OnPlayAlbum = a.onPlayAlbum a.grid.OnShowArtistPage = a.onShowArtistPage a.container = container.NewBorder( @@ -89,8 +90,9 @@ func (a *AlbumsPage) OnSearched(query string) { return } if a.searchGrid == nil { - a.searchGrid = NewAlbumGrid(a.lm.SearchIter(query), a.im.GetAlbumThumbnail) + a.searchGrid = NewAlbumGrid(a.lm.SearchIter(query), a.im.GetAlbumThumbnail, false /*showYear*/) a.searchGrid.OnPlayAlbum = a.onPlayAlbum + a.searchGrid.OnShowArtistPage = a.onShowArtistPage } else { a.searchGrid.Reset(a.lm.SearchIter(query)) } diff --git a/ui/artistpage.go b/ui/artistpage.go index 80232c7..494b8ff 100644 --- a/ui/artistpage.go +++ b/ui/artistpage.go @@ -60,7 +60,7 @@ func (a *ArtistPage) loadAsync() { } a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name a.titleDisp.Refresh() - ag := NewFixedAlbumGrid(artist.Album, a.im.GetAlbumThumbnail) + ag := NewFixedAlbumGrid(artist.Album, a.im.GetAlbumThumbnail, true /*showYear*/) ag.OnPlayAlbum = a.onPlayAlbum a.container.Objects[0] = ag a.container.Refresh() diff --git a/ui/widgets/albumcard.go b/ui/widgets/albumcard.go index 7a49536..8502422 100644 --- a/ui/widgets/albumcard.go +++ b/ui/widgets/albumcard.go @@ -3,6 +3,7 @@ package widgets import ( "context" "image" + "strconv" "supersonic/ui/layout" @@ -65,8 +66,11 @@ type AlbumCard struct { artistID string title *widget.Label artist *CustomHyperlink + year *widget.Label container *fyne.Container + showYear bool + // updated by AlbumGrid Cover *albumCover @@ -85,11 +89,13 @@ func (a *AlbumCard) MouseOut() {} func (a *AlbumCard) MouseMoved(*desktop.MouseEvent) {} -func NewAlbumCard() *AlbumCard { +func NewAlbumCard(showYear bool) *AlbumCard { a := &AlbumCard{ - title: widget.NewLabel(""), - artist: NewCustomHyperlink(), - Cover: newAlbumCover(), + title: widget.NewLabel(""), + artist: NewCustomHyperlink(), + year: widget.NewLabel(""), + Cover: newAlbumCover(), + showYear: showYear, } a.ExtendBaseWidget(a) a.Cover.OnDoubleTapped = func() { @@ -110,8 +116,12 @@ func NewAlbumCard() *AlbumCard { } func (a *AlbumCard) createContainer() { - titleArtist := container.New(&layout.VboxCustomPadding{ExtraPad: -16}, a.title, a.artist) - c := container.New(&layout.VboxCustomPadding{ExtraPad: -5}, a.Cover, titleArtist) + var secondLabel fyne.Widget = a.artist + if a.showYear { + secondLabel = a.year + } + info := container.New(&layout.VboxCustomPadding{ExtraPad: -16}, a.title, secondLabel) + c := container.New(&layout.VboxCustomPadding{ExtraPad: -5}, a.Cover, info) pad := &layout.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10} a.container = container.New(pad, c) } @@ -119,6 +129,7 @@ func (a *AlbumCard) createContainer() { func (a *AlbumCard) Update(al *subsonic.AlbumID3) { a.title.SetText(al.Name) a.artist.SetText(al.Artist) + a.year.SetText(strconv.Itoa(al.Year)) a.albumID = al.ID a.artistID = al.ArtistID }