From 4f002574c237b52fdd29d165ba1e17014fc68483 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 22 Oct 2023 11:40:06 -0700 Subject: [PATCH] handle multiple artists properly in UI --- backend/mpmedia_mac.go | 3 ++- sharedutil/sharedutil.go | 12 ++++++++++++ ui/bottompanel.go | 8 ++++---- ui/browsing/artistpage.go | 2 +- ui/browsing/artistspage.go | 2 +- ui/browsing/favoritespage.go | 2 +- ui/browsing/playlistspage.go | 2 +- ui/mainwindow.go | 3 ++- ui/widgets/gridview.go | 14 +++++++------- ui/widgets/gridviewitem.go | 29 +++++++++++++---------------- ui/widgets/multihyperlink.go | 28 ++++++++++++++++++++++------ ui/widgets/nowplayingcard.go | 10 ++++------ ui/widgets/tracklist.go | 6 +++--- 13 files changed, 73 insertions(+), 48 deletions(-) diff --git a/backend/mpmedia_mac.go b/backend/mpmedia_mac.go index 7848856..4a38814 100644 --- a/backend/mpmedia_mac.go +++ b/backend/mpmedia_mac.go @@ -18,6 +18,7 @@ import ( import ( "fmt" "log" + "strings" "unsafe" "github.com/dweymouth/supersonic/backend/mediaprovider" @@ -79,7 +80,7 @@ func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) var artist string if len(track.ArtistNames) > 0 { - artist = track.ArtistNames[0] + artist = strings.Join(track.ArtistNames, ", ") } cArtist := C.CString(artist) diff --git a/sharedutil/sharedutil.go b/sharedutil/sharedutil.go index 35cd5cb..8710813 100644 --- a/sharedutil/sharedutil.go +++ b/sharedutil/sharedutil.go @@ -7,6 +7,18 @@ import ( "github.com/dweymouth/supersonic/backend/mediaprovider" ) +func SliceEqual[T comparable](a []T, b []T) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + func SliceContains[T comparable](ts []T, t T) bool { for _, x := range ts { if x == t { diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 69fe61d..bf4fc2a 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -76,8 +76,8 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro bp.NowPlaying.OnAlbumNameTapped(func() { contr.NavigateTo(controller.AlbumRoute(bp.playbackManager.NowPlaying().AlbumID)) }) - bp.NowPlaying.OnArtistNameTapped(func() { - contr.NavigateTo(controller.ArtistRoute(bp.playbackManager.NowPlaying().ArtistIDs[0])) + bp.NowPlaying.OnArtistNameTapped(func(artistID string) { + contr.NavigateTo(controller.ArtistRoute(artistID)) }) bp.NowPlaying.OnTrackNameTapped(func() { contr.NavigateTo(controller.NowPlayingRoute(bp.playbackManager.NowPlaying().ID)) @@ -113,7 +113,7 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro func (bp *BottomPanel) onSongChange(song, _ *mediaprovider.Track) { if song == nil { - bp.NowPlaying.Update("", "", false, "", nil) + bp.NowPlaying.Update("", []string{}, []string{}, "", nil) } else { bp.coverArtID = song.CoverArtID var im image.Image @@ -125,7 +125,7 @@ func (bp *BottomPanel) onSongChange(song, _ *mediaprovider.Track) { imgTTLSec := song.Duration + 30 im, _ = bp.ImageManager.GetCoverThumbnailWithTTL(song.CoverArtID, time.Duration(imgTTLSec)*time.Second) } - bp.NowPlaying.Update(song.Name, song.ArtistNames[0], song.ArtistIDs[0] != "", song.Album, im) + bp.NowPlaying.Update(song.Name, song.ArtistNames, song.ArtistIDs, song.Album, im) } } diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index b4d2c47..80d4979 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -189,7 +189,7 @@ func (a *ArtistPage) showAlbumGrid() { Name: al.Name, ID: al.ID, CoverArtID: al.CoverArtID, - Secondary: strconv.Itoa(al.Year), + Secondary: []string{strconv.Itoa(al.Year)}, } }) if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil { diff --git a/ui/browsing/artistspage.go b/ui/browsing/artistspage.go index 1fffc4a..1d039d0 100644 --- a/ui/browsing/artistspage.go +++ b/ui/browsing/artistspage.go @@ -158,7 +158,7 @@ func createArtistsGridViewModel(artists []*mediaprovider.Artist) []widgets.GridV Name: ar.Name, ID: ar.ID, CoverArtID: ar.CoverArtID, - Secondary: fmt.Sprintf("%d %s", ar.AlbumCount, albums), + Secondary: []string{fmt.Sprintf("%d %s", ar.AlbumCount, albums)}, } }) } diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 82758b6..a4406fe 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -333,7 +333,7 @@ func buildArtistGridViewModel(artists []*mediaprovider.Artist) []widgets.GridVie ID: ar.ID, CoverArtID: ar.CoverArtID, Name: ar.Name, - Secondary: fmt.Sprintf("%d %s", ar.AlbumCount, albums), + Secondary: []string{fmt.Sprintf("%d %s", ar.AlbumCount, albums)}, }) } return model diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index 4d6214e..dbf9b92 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -175,7 +175,7 @@ func createPlaylistGridViewModel(playlists []*mediaprovider.Playlist) []widgets. Name: pl.Name, ID: pl.ID, CoverArtID: pl.CoverArtID, - Secondary: fmt.Sprintf("%d %s", pl.TrackCount, tracks), + Secondary: []string{fmt.Sprintf("%d %s", pl.TrackCount, tracks)}, } }) } diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 7eb8860..765cbd4 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -2,6 +2,7 @@ package ui import ( "fmt" + "strings" "github.com/20after4/configdir" "github.com/dweymouth/supersonic/backend" @@ -86,7 +87,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, m.Window.SetTitle(displayAppName) return } - m.Window.SetTitle(fmt.Sprintf("%s – %s · %s", song.Name, song.ArtistNames[0], displayAppName)) + m.Window.SetTitle(fmt.Sprintf("%s – %s · %s", song.Name, strings.Join(song.ArtistNames, ", "), displayAppName)) }) app.ServerManager.OnServerConnected(func() { m.BrowsingPane.EnableNavigationButtons() diff --git a/ui/widgets/gridview.go b/ui/widgets/gridview.go index 6b69322..cd49670 100644 --- a/ui/widgets/gridview.go +++ b/ui/widgets/gridview.go @@ -55,11 +55,11 @@ func (g gridViewAlbumIterator) NextN(n int) []GridViewItemModel { albums := g.iter.NextN(n) return sharedutil.MapSlice(albums, func(al *mediaprovider.Album) GridViewItemModel { return GridViewItemModel{ - Name: al.Name, - ID: al.ID, - CoverArtID: al.CoverArtID, - Secondary: al.ArtistNames[0], - SecondaryID: al.ArtistIDs[0], + Name: al.Name, + ID: al.ID, + CoverArtID: al.CoverArtID, + Secondary: al.ArtistNames, + SecondaryIDs: al.ArtistIDs, } }) } @@ -205,9 +205,9 @@ func (g *GridView) createGridWrap() { func() fyne.CanvasObject { card := NewGridViewItem(g.Placeholder) card.OnPlay = func() { g.onPlay(card.ItemID(), false) } - card.OnShowSecondaryPage = func() { + card.OnShowSecondaryPage = func(id string) { if g.OnShowSecondaryPage != nil { - g.OnShowSecondaryPage(card.SecondaryID()) + g.OnShowSecondaryPage(id) } } card.OnShowItemPage = func() { diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index ae062c7..cfa9bf1 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -5,6 +5,7 @@ import ( "image" "github.com/dweymouth/supersonic/res" + "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/layouts" "fyne.io/fyne/v2" @@ -122,18 +123,18 @@ func isInside(origin fyne.Position, radius float32, point fyne.Position) bool { } type GridViewItemModel struct { - Name string - ID string - CoverArtID string - Secondary string - SecondaryID string + Name string + ID string + CoverArtID string + Secondary []string + SecondaryIDs []string } type GridViewItem struct { widget.BaseWidget itemID string - secondaryID string + secondaryIDs []string primaryText *widget.Hyperlink secondaryText *MultiHyperlink container *fyne.Container @@ -145,7 +146,7 @@ type GridViewItem struct { OnPlay func() OnShowContextMenu func(fyne.Position) OnShowItemPage func() - OnShowSecondaryPage func() + OnShowSecondaryPage func(string) } func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { @@ -174,9 +175,9 @@ func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { } g.Cover.OnShowPage = showItemFn g.primaryText.OnTapped = showItemFn - g.secondaryText.OnTapped = func(_ string) { // TODO + g.secondaryText.OnTapped = func(s string) { if g.OnShowSecondaryPage != nil { - g.OnShowSecondaryPage() + g.OnShowSecondaryPage(s) } } @@ -192,14 +193,14 @@ func (g *GridViewItem) createContainer() { } func (g *GridViewItem) NeedsUpdate(model GridViewItemModel) bool { - return g.itemID != model.ID || g.secondaryID != model.SecondaryID + return g.itemID != model.ID || !sharedutil.SliceEqual(g.secondaryIDs, model.SecondaryIDs) } func (g *GridViewItem) Update(model GridViewItemModel) { g.itemID = model.ID - g.secondaryID = model.SecondaryID + g.secondaryIDs = model.SecondaryIDs g.primaryText.SetText(model.Name) - g.secondaryText.Segments = []MultiHyperlinkSegment{{Text: model.Secondary, LinkID: model.SecondaryID}} + g.secondaryText.BuildSegments(model.Secondary, model.SecondaryIDs) g.secondaryText.Refresh() g.Cover.ResetPlayButton() } @@ -212,10 +213,6 @@ func (g *GridViewItem) ItemID() string { return g.itemID } -func (g *GridViewItem) SecondaryID() string { - return g.secondaryID -} - func (g *GridViewItem) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(g.container) } diff --git a/ui/widgets/multihyperlink.go b/ui/widgets/multihyperlink.go index 2770316..743042d 100644 --- a/ui/widgets/multihyperlink.go +++ b/ui/widgets/multihyperlink.go @@ -38,6 +38,18 @@ func NewMultiHyperlink() *MultiHyperlink { return c } +func (m *MultiHyperlink) BuildSegments(texts, links []string) { + l := len(links) + m.Segments = nil + for i, text := range texts { + link := "" + if l > i { + link = links[i] + } + m.Segments = append(m.Segments, MultiHyperlinkSegment{Text: text, LinkID: link}) + } +} + func (c *MultiHyperlink) getMinSegWidth() float32 { if c.minSegWidthCached == 0 { c.minSegWidthCached = fyne.MeasureText(", W", theme.TextSize(), fyne.TextStyle{}).Width @@ -82,7 +94,7 @@ func (c *MultiHyperlink) layoutObjects() { if seg.LinkID == "" { obj = c.updateOrReplaceLabel(obj, seg.Text) } else { - obj = c.updateOrReplaceHyperlink(obj, seg.Text) + obj = c.updateOrReplaceHyperlink(obj, seg.Text, seg.LinkID) } if appendingSegments { c.content.Objects = append(c.content.Objects, obj) @@ -124,15 +136,17 @@ func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string return l } -func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text string) fyne.CanvasObject { +func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text, link string) fyne.CanvasObject { if obj != nil { - if link, ok := obj.(*widget.Hyperlink); ok { - link.Text = text - return link + if l, ok := obj.(*widget.Hyperlink); ok { + l.Text = text + l.OnTapped = func() { c.onSegmentTapped(link) } + return l } } l := widget.NewHyperlink(text, nil) l.Wrapping = fyne.TextTruncate + l.OnTapped = func() { c.onSegmentTapped(link) } return l } @@ -200,7 +214,9 @@ func (c *MultiHyperlink) updateOrReplaceHyperlinkSegment(seg widget.RichTextSegm */ func (c *MultiHyperlink) onSegmentTapped(linkID string) { - // TODO + if c.OnTapped != nil { + c.OnTapped(linkID) + } } func (c *MultiHyperlink) MinSize() fyne.Size { diff --git a/ui/widgets/nowplayingcard.go b/ui/widgets/nowplayingcard.go index 1af563a..0ed7569 100644 --- a/ui/widgets/nowplayingcard.go +++ b/ui/widgets/nowplayingcard.go @@ -41,7 +41,6 @@ func NewNowPlayingCard() *NowPlayingCard { n.cover = NewTappableImage(n.onShowCoverImage) n.cover.OnTappedSecondary = n.showMenu n.trackName.Hidden = true - n.artistName.Hidden = true n.albumName.Hidden = true n.albumName.Wrapping = fyne.TextTruncate n.trackName.Wrapping = fyne.TextTruncate @@ -85,19 +84,18 @@ func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(n.c) } -func (n *NowPlayingCard) Update(track, artist string, artistNavigable bool, album string, cover image.Image) { +func (n *NowPlayingCard) Update(track string, artists, artistIDs []string, album string, cover image.Image) { n.trackName.SetText(track) n.trackName.Hidden = track == "" - n.artistName.Segments = []MultiHyperlinkSegment{{Text: artist}} - n.artistName.Hidden = artist == "" + n.artistName.BuildSegments(artists, artistIDs) n.albumName.SetText(album) n.albumName.Hidden = album == "" n.cover.Image.Image = cover n.c.Refresh() } -func (n *NowPlayingCard) OnArtistNameTapped(f func()) { - //n.artistName.OnTapped = f +func (n *NowPlayingCard) OnArtistNameTapped(f func(string)) { + n.artistName.OnTapped = f } func (n *NowPlayingCard) OnAlbumNameTapped(f func()) { diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index c34ea29..acd8ecc 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -396,7 +396,7 @@ func (t *Tracklist) doSortTracks() { case ColumnTitle: t.stringSort(func(tr *trackModel) string { return tr.track.Name }) case ColumnArtist: - t.stringSort(func(tr *trackModel) string { return tr.track.ArtistNames[0] }) + t.stringSort(func(tr *trackModel) string { return strings.Join(tr.track.ArtistNames, ", ") }) case ColumnAlbum: t.stringSort(func(tr *trackModel) string { return tr.track.Album }) case ColumnPath: @@ -698,7 +698,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.num = newTrailingAlignRichText() t.name = newTruncatingRichText() t.artist = NewMultiHyperlink() - //t.artist.OnTapped = func() { tracklist.onArtistTapped(t.artistID) } // TODO + t.artist.OnTapped = tracklist.onArtistTapped t.album = widget.NewHyperlink("", nil) t.album.Wrapping = fyne.TextTruncate t.album.OnTapped = func() { tracklist.onAlbumTapped(t.albumID) } @@ -748,7 +748,7 @@ func (t *TrackRow) Update(tm *trackModel, rowNum int) { t.albumID = tr.AlbumID t.name.Segments[0].(*widget.TextSegment).Text = tr.Name - t.artist.Segments = []MultiHyperlinkSegment{{Text: tr.ArtistNames[0], LinkID: tr.ArtistIDs[0]}} + t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs) t.album.SetText(tr.Album) t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) t.year.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.Year)