From f26e9dbcbf66dbb0caa80aab00e302ff88a91adf Mon Sep 17 00:00:00 2001 From: natilou <30585029+natilou@users.noreply.github.com> Date: Wed, 21 Jun 2023 23:20:40 -0300 Subject: [PATCH 1/3] Add UI for viewing the album info returned by Subsonic getAlbumInfo API - Adding "Get Info" in album page's "meatball" menu. When the option is clicked a pop up appears with the album information and links to Last.fm and MusicBrainz. Screenshot: Fixes #105 --- backend/mediaprovider/mediaprovider.go | 2 + backend/mediaprovider/model.go | 6 + .../subsonic/subsonicmediaprovider.go | 13 ++ ui/browsing/albumpage.go | 4 +- ui/controller/controller.go | 19 +++ ui/dialogs/albuminfodialog.go | 111 ++++++++++++++++++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 ui/dialogs/albuminfodialog.go diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index 9b2fd3c..86a3880 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -39,6 +39,8 @@ type MediaProvider interface { GetAlbum(albumID string) (*AlbumWithTracks, error) + GetAlbumInfo(albumID string) (*AlbumInfo, error) + GetArtist(artistID string) (*ArtistWithAlbums, error) GetArtistInfo(artistID string) (*ArtistInfo, error) diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index d6ffa63..41858ac 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -18,6 +18,12 @@ type AlbumWithTracks struct { Tracks []*Track } +type AlbumInfo struct { + Info string + LastFMUrl string + MusicBrainzID string +} + type Artist struct { ID string CoverArtID string diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 446378f..b33d922 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -59,6 +59,19 @@ func (s *subsonicMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWi return album, nil } +func (s *subsonicMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.AlbumInfo, error) { + al, err := s.client.GetAlbumInfo(albumID) + if err != nil { + return nil, err + } + album := &mediaprovider.AlbumInfo{ + Info: al.Notes, + LastFMUrl: al.LastFmUrl, + MusicBrainzID: al.MusicBrainzID, + } + return album, nil +} + func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) { ar, err := s.client.GetArtist(artistID) if err != nil { diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 22c3f62..1cb93e4 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -203,7 +203,9 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { }), fyne.NewMenuItem("Download...", func() { a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String()) - + }), + fyne.NewMenuItem("Get Info...", func() { + a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image.Image) })) pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a)) } diff --git a/ui/controller/controller.go b/ui/controller/controller.go index a365c94..fc564c6 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -651,3 +651,22 @@ func (c *Controller) sendNotification(title, content string) { Content: content, }) } + +func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover image.Image) { + go func() { + albumInfo, err := c.App.ServerManager.Server.GetAlbumInfo(albumID) + if err != nil { + log.Print("Error getting album info: ", err) + return + } + dlg := dialogs.NewAlbumInfoDialog(albumInfo, albumName, albumCover) + pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas()) + dlg.OnDismiss = func() { + pop.Hide() + c.doModalClosed() + } + c.ClosePopUpOnEscape(pop) + c.haveModal = true + pop.Show() + }() +} diff --git a/ui/dialogs/albuminfodialog.go b/ui/dialogs/albuminfodialog.go new file mode 100644 index 0000000..12004b8 --- /dev/null +++ b/ui/dialogs/albuminfodialog.go @@ -0,0 +1,111 @@ +package dialogs + +import ( + "fmt" + "image" + "net/url" + "strings" + + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/ui/layouts" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +type AlbumInfoDialog struct { + widget.BaseWidget + + OnDismiss func() + + content fyne.CanvasObject +} + +func NewAlbumInfoDialog(albumInfo *mediaprovider.AlbumInfo, albumName string, albumCover image.Image) *AlbumInfoDialog { + a := &AlbumInfoDialog{} + a.ExtendBaseWidget(a) + + a.content = container.NewVBox( + a.buildMainContainer(albumInfo, albumName, albumCover), + widget.NewSeparator(), + container.NewHBox( + layout.NewSpacer(), + widget.NewButton("Close", func() { + if a.OnDismiss != nil { + a.OnDismiss() + } + }), + ), + ) + a.content.Resize(a.MinSize()) + return a +} + +func (a *AlbumInfoDialog) MinSize() fyne.Size { + return fyne.NewSize(550, a.BaseWidget.MinSize().Height) +} + +func (a *AlbumInfoDialog) buildMainContainer(albumInfo *mediaprovider.AlbumInfo, albumName string, albumCover image.Image) *fyne.Container { + iconImage := canvas.NewImageFromImage(albumCover) + iconImage.FillMode = canvas.ImageFillContain + iconImage.SetMinSize(fyne.NewSize(100, 100)) + title := widget.NewRichTextWithText(albumName) + title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true + title.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameSubHeadingText + title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter + + infoContent := widget.NewLabel("Album info not available") + + if albumInfo.Info != "" { + infoContent = a.infoLabel(albumInfo.Info) + } + + urlContainer := a.buildUrlContainer(albumInfo.LastFMUrl, albumInfo.MusicBrainzID) + + return container.NewVBox( + iconImage, + title, + infoContent, + urlContainer, + ) + +} + +func (a *AlbumInfoDialog) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.content) +} + +func (a *AlbumInfoDialog) infoLabel(info string) *widget.Label { + last := strings.LastIndex(info, " 0 { + urlContainer.Add(widget.NewLabel("ยท")) + } + urlContainer.Add(url) + } + + return container.NewCenter(urlContainer) +} From 681fa0625582c24d93903bb8e5700697f6eea0c0 Mon Sep 17 00:00:00 2001 From: natilou <30585029+natilou@users.noreply.github.com> Date: Mon, 26 Jun 2023 09:54:27 -0300 Subject: [PATCH 2/3] changing AlbumInfo attributes name, using the same names as subsonic API. Checking if lastFm and musicBrainzID are not empty strings. Saving the musicbrainz release url in constant. Using 'Show Info...' in meatball menu. --- backend/mediaprovider/model.go | 4 ++-- .../subsonic/subsonicmediaprovider.go | 4 ++-- ui/browsing/albumpage.go | 2 +- ui/dialogs/albuminfodialog.go | 23 ++++++++++++------- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index 41858ac..91e5837 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -19,8 +19,8 @@ type AlbumWithTracks struct { } type AlbumInfo struct { - Info string - LastFMUrl string + Notes string + LastFmUrl string MusicBrainzID string } diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index b33d922..26993d4 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -65,8 +65,8 @@ func (s *subsonicMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.Alb return nil, err } album := &mediaprovider.AlbumInfo{ - Info: al.Notes, - LastFMUrl: al.LastFmUrl, + Notes: al.Notes, + LastFmUrl: al.LastFmUrl, MusicBrainzID: al.MusicBrainzID, } return album, nil diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 1cb93e4..a7ffa91 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -204,7 +204,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { fyne.NewMenuItem("Download...", func() { a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String()) }), - fyne.NewMenuItem("Get Info...", func() { + fyne.NewMenuItem("Show Info...", func() { a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image.Image) })) pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a)) diff --git a/ui/dialogs/albuminfodialog.go b/ui/dialogs/albuminfodialog.go index 12004b8..93fbe1d 100644 --- a/ui/dialogs/albuminfodialog.go +++ b/ui/dialogs/albuminfodialog.go @@ -18,6 +18,8 @@ import ( "fyne.io/fyne/v2/widget" ) +const musicBrainzReleaseUrl = "https://musicbrainz.org/release" + type AlbumInfoDialog struct { widget.BaseWidget @@ -61,11 +63,11 @@ func (a *AlbumInfoDialog) buildMainContainer(albumInfo *mediaprovider.AlbumInfo, infoContent := widget.NewLabel("Album info not available") - if albumInfo.Info != "" { - infoContent = a.infoLabel(albumInfo.Info) + if albumInfo.Notes != "" { + infoContent = a.infoLabel(albumInfo.Notes) } - urlContainer := a.buildUrlContainer(albumInfo.LastFMUrl, albumInfo.MusicBrainzID) + urlContainer := a.buildUrlContainer(albumInfo.LastFmUrl, albumInfo.MusicBrainzID) return container.NewVBox( iconImage, @@ -89,14 +91,19 @@ func (a *AlbumInfoDialog) infoLabel(info string) *widget.Label { return lbl } -func (a *AlbumInfoDialog) buildUrlContainer(lastFM, musicBrainzID string) *fyne.Container { +func (a *AlbumInfoDialog) buildUrlContainer(lastFm, musicBrainzID string) *fyne.Container { urls := make([]*widget.Hyperlink, 0) - if lastFMUrl, err := url.Parse(lastFM); err == nil { - urls = append(urls, widget.NewHyperlink("Last.fm", lastFMUrl)) + if lastFm != "" { + if lastFmUrl, err := url.Parse(lastFm); err == nil { + urls = append(urls, widget.NewHyperlink("Last.fm", lastFmUrl)) + } } - if musicBrainzUrl, err := url.Parse(fmt.Sprintf("https://musicbrainz.org/release/%s", musicBrainzID)); err == nil { - urls = append(urls, widget.NewHyperlink("MusicBrainz", musicBrainzUrl)) + + if musicBrainzID != "" { + if musicBrainzUrl, err := url.Parse(fmt.Sprintf("%s/%s", musicBrainzReleaseUrl, musicBrainzID)); err == nil { + urls = append(urls, widget.NewHyperlink("MusicBrainz", musicBrainzUrl)) + } } urlContainer := container.New(&layouts.HboxCustomPadding{DisableThemePad: true, ExtraPad: -10}) From 92416017379f7755f5ba364657c4cbf025c2eef6 Mon Sep 17 00:00:00 2001 From: natilou <30585029+natilou@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:47:22 -0300 Subject: [PATCH 3/3] removing Resize line and wrapping the VBox in another container with layouts.MaxPadLayout --- ui/dialogs/albuminfodialog.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/dialogs/albuminfodialog.go b/ui/dialogs/albuminfodialog.go index 93fbe1d..9839582 100644 --- a/ui/dialogs/albuminfodialog.go +++ b/ui/dialogs/albuminfodialog.go @@ -44,7 +44,6 @@ func NewAlbumInfoDialog(albumInfo *mediaprovider.AlbumInfo, albumName string, al }), ), ) - a.content.Resize(a.MinSize()) return a } @@ -69,11 +68,14 @@ func (a *AlbumInfoDialog) buildMainContainer(albumInfo *mediaprovider.AlbumInfo, urlContainer := a.buildUrlContainer(albumInfo.LastFmUrl, albumInfo.MusicBrainzID) - return container.NewVBox( - iconImage, - title, - infoContent, - urlContainer, + return container.New( + &layouts.MaxPadLayout{PadLeft: 15, PadRight: 10, PadTop: 15, PadBottom: 10}, + container.NewVBox( + iconImage, + title, + infoContent, + urlContainer, + ), ) }