From bb91a0c6b4242f11976032d9de99b77326aaa7c9 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 14 Jul 2024 16:46:07 -0700 Subject: [PATCH 1/3] WIP: add track info dialog --- ui/controller/controller.go | 18 +++++++ ui/dialogs/trackinfodialog.go | 94 +++++++++++++++++++++++++++++++++++ ui/widgets/tracklist.go | 11 +++- 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 ui/dialogs/trackinfodialog.go diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 08f30e7..41affdc 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -146,6 +146,7 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget tracklist.OnShare = func(trackID string) { go m.ShowShareDialog(trackID) } + tracklist.OnShowTrackInfo = m.ShowTrackInfoDialog tracklist.OnPlaySongRadio = func(track *mediaprovider.Track) { go func() { tracks, err := m.GetSongRadioTracks(track) @@ -858,6 +859,23 @@ func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover i }() } +func (c *Controller) ShowTrackInfoDialog(track *mediaprovider.Track) { + info := dialogs.NewTrackInfoDialog(track) + pop := widget.NewModalPopUp(info, c.MainWindow.Canvas()) + info.OnDismiss = func() { + pop.Hide() + c.doModalClosed() + } + c.ClosePopUpOnEscape(pop) + winSize := c.MainWindow.Canvas().Size() + popMin := pop.MinSize() + width := fyne.Max(700, fyne.Max(popMin.Width, winSize.Width*0.6)) + height := fyne.Max(500, fyne.Max(popMin.Height, winSize.Height*0.7)) + pop.Resize(fyne.NewSize(width, height)) + c.haveModal = true + pop.Show() +} + func (c *Controller) GetSongRadioTracks(sourceTrack *mediaprovider.Track) ([]*mediaprovider.Track, error) { radioTracks, err := c.App.ServerManager.Server.GetSongRadio(sourceTrack.ID, 100) if err != nil { diff --git a/ui/dialogs/trackinfodialog.go b/ui/dialogs/trackinfodialog.go new file mode 100644 index 0000000..e634841 --- /dev/null +++ b/ui/dialogs/trackinfodialog.go @@ -0,0 +1,94 @@ +package dialogs + +import ( + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/ui/util" + "github.com/dweymouth/supersonic/ui/widgets" +) + +type TrackInfoDialog struct { + widget.BaseWidget + + OnDismiss func() + OnNavigateToArtist func(artistID string) + OnNavigateToAlbum func(albumID string) + + track *mediaprovider.Track +} + +func NewTrackInfoDialog(track *mediaprovider.Track) *TrackInfoDialog { + t := &TrackInfoDialog{track: track} + t.ExtendBaseWidget(t) + return t +} + +func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { + c := container.New(layout.NewFormLayout()) + c.Add(newFormText("Title", true)) + c.Add(newFormText(t.track.Title, false)) + + c.Add(newFormText("Artist", true)) + artists := widgets.NewMultiHyperlink() + artists.BuildSegments(t.track.ArtistNames, t.track.ArtistIDs) + artists.OnTapped = func(id string) { + if t.OnNavigateToArtist != nil { + t.OnNavigateToArtist(id) + } + } + c.Add(artists) + + c.Add(newFormText("Album", true)) + album := widget.NewHyperlink(t.track.Album, nil) + album.OnTapped = func() { + if t.OnNavigateToAlbum != nil { + t.OnNavigateToAlbum(t.track.AlbumID) + } + } + c.Add(album) + + c.Add(newFormText("File Path", true)) + path := widgets.NewMaxRowsLabel(2, t.track.FilePath) + //path.Segments[0].(*widget.TextSegment).Style.SizeName = myTheme.SizeNameSubText + path.Wrapping = fyne.TextWrapWord + path.Truncation = fyne.TextTruncateEllipsis + c.Add(path) + + c.Add(newFormText("Comment", true)) + c.Add(newFormText(t.track.Comment, false)) + + c.Add(newFormText("Year", true)) + c.Add(newFormText(strconv.Itoa(t.track.Year), false)) + + c.Add(newFormText("Bit Rate", true)) + c.Add(newFormText(strconv.Itoa(t.track.BitRate)+"kbps", false)) + + c.Add(newFormText("File Size", true)) + c.Add(newFormText(util.BytesToSizeString(t.track.Size), false)) + + c.Add(newFormText("Play Count", true)) + c.Add(newFormText(strconv.Itoa(t.track.PlayCount), false)) + + return widget.NewSimpleRenderer(c) +} + +func newFormText(text string, leftCol bool) *widget.RichText { + alignment := fyne.TextAlignLeading + if leftCol { + alignment = fyne.TextAlignTrailing + } + return widget.NewRichText( + &widget.TextSegment{ + Text: text, + Style: widget.RichTextStyle{ + TextStyle: fyne.TextStyle{Bold: leftCol}, + Alignment: alignment, + }, + }, + ) +} diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 6cd9c55..3c703b6 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -78,6 +78,7 @@ type Tracklist struct { OnShare func(trackID string) OnPlaySongRadio func(track *mediaprovider.Track) OnReorderTracks func(trackIDs []string, insertPos int) + OnShowTrackInfo func(track *mediaprovider.Track) OnShowArtistPage func(artistID string) OnShowAlbumPage func(albumID string) @@ -101,6 +102,7 @@ type Tracklist struct { ratingSubmenu *fyne.MenuItem shareMenuItem *fyne.MenuItem songRadioMenuItem *fyne.MenuItem + infoMenuItem *fyne.MenuItem container *fyne.Container } @@ -559,6 +561,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { t.onDownload(t.selectedTracks(), "Selected tracks") }) download.Icon = theme.DownloadIcon() + t.infoMenuItem = fyne.NewMenuItem("Show info...", func() { + if t.OnShowTrackInfo != nil { + t.OnShowTrackInfo(t.selectedTracks()[0]) + } + }) + t.infoMenuItem.Icon = theme.InfoIcon() favorite := fyne.NewMenuItem("Set favorite", func() { t.onSetFavorites(t.selectedTracks(), true, true) }) @@ -568,7 +576,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { }) unfavorite.Icon = myTheme.NotFavoriteIcon t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItemSeparator(), - playlist, download) + playlist, download, t.infoMenuItem) t.shareMenuItem = fyne.NewMenuItem("Share...", func() { t.onShare(t.selectedTracks()) }) @@ -587,6 +595,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { } t.ratingSubmenu.Disabled = t.Options.DisableRating t.shareMenuItem.Disabled = t.Options.DisableSharing || len(t.selectedTracks()) != 1 + t.infoMenuItem.Disabled = len(t.selectedTracks()) != 1 widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition) } From 03ce1cc115ab20e84a814913af2822a147c3677f Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 18 Jul 2024 08:11:37 -0700 Subject: [PATCH 2/3] add more info to dialog and adjust layout --- backend/mediaprovider/model.go | 10 +++ .../subsonic/subsonicmediaprovider.go | 9 +++ go.mod | 2 +- go.sum | 2 + ui/dialogs/trackinfodialog.go | 71 +++++++++++++------ 5 files changed, 73 insertions(+), 21 deletions(-) diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index 40390ba..9d80a74 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -102,7 +102,17 @@ type Track struct { PlayCount int FilePath string BitRate int + ContentType string Comment string + BPM int + ReplayGain ReplayGainInfo +} + +type ReplayGainInfo struct { + TrackGain float64 + AlbumGain float64 + TrackPeak float64 + AlbumPeak float64 } type Playlist struct { diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 45fa8c2..d38f7bb 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -493,6 +493,13 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { artistIDs = append(artistIDs, ch.ArtistID) } + var rGain mediaprovider.ReplayGainInfo + if rg := ch.ReplayGain; rg != nil { + rGain.AlbumGain = rg.AlbumGain + rGain.TrackGain = rg.TrackGain + rGain.AlbumPeak = rg.AlbumPeak + rGain.TrackPeak = rg.TrackPeak + } return &mediaprovider.Track{ ID: ch.ID, CoverArtID: ch.CoverArt, @@ -513,7 +520,9 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { FilePath: ch.Path, Size: ch.Size, BitRate: ch.BitRate, + ContentType: ch.ContentType, Comment: ch.Comment, + BPM: ch.BPM, } } diff --git a/go.mod b/go.mod index 2f438d5..7eb526a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee - github.com/dweymouth/go-subsonic v0.0.0-20240603150834-605046e7c78a + github.com/dweymouth/go-subsonic v0.0.0-20240716154859-c40a4519e1e0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.3.0 github.com/pelletier/go-toml/v2 v2.0.8 diff --git a/go.sum b/go.sum index aa552b0..dd21869 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20240603150834-605046e7c78a h1:jcHDGzvpsyvEt8i0vqB92jHIfWJUt106QITrPoYjSWo= github.com/dweymouth/go-subsonic v0.0.0-20240603150834-605046e7c78a/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo= +github.com/dweymouth/go-subsonic v0.0.0-20240716154859-c40a4519e1e0 h1:Zf27NWT1N889/toYK7/x7y7ERw8iJfAHBHGiMyYxaLY= +github.com/dweymouth/go-subsonic v0.0.0-20240716154859-c40a4519e1e0/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= diff --git a/ui/dialogs/trackinfodialog.go b/ui/dialogs/trackinfodialog.go index e634841..4553ad9 100644 --- a/ui/dialogs/trackinfodialog.go +++ b/ui/dialogs/trackinfodialog.go @@ -1,6 +1,7 @@ package dialogs import ( + "fmt" "strconv" "fyne.io/fyne/v2" @@ -30,8 +31,8 @@ func NewTrackInfoDialog(track *mediaprovider.Track) *TrackInfoDialog { func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { c := container.New(layout.NewFormLayout()) - c.Add(newFormText("Title", true)) - c.Add(newFormText(t.track.Title, false)) + + addFormRow(c, "Title", t.track.Title) c.Add(newFormText("Artist", true)) artists := widgets.NewMultiHyperlink() @@ -52,29 +53,55 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { } c.Add(album) - c.Add(newFormText("File Path", true)) - path := widgets.NewMaxRowsLabel(2, t.track.FilePath) - //path.Segments[0].(*widget.TextSegment).Style.SizeName = myTheme.SizeNameSubText - path.Wrapping = fyne.TextWrapWord - path.Truncation = fyne.TextTruncateEllipsis - c.Add(path) + addFormRow(c, "Duration", util.SecondsToTimeString(float64(t.track.Duration))) + addFormRow(c, "File path", t.track.FilePath) + addFormRow(c, "Comment", t.track.Comment) + addFormRow(c, "Year", strconv.Itoa(t.track.Year)) + addFormRow(c, "Track number", strconv.Itoa(t.track.TrackNumber)) + addFormRow(c, "Disc number", strconv.Itoa(t.track.DiscNumber)) - c.Add(newFormText("Comment", true)) - c.Add(newFormText(t.track.Comment, false)) + if t.track.BPM > 0 { + addFormRow(c, "BPM", strconv.Itoa(t.track.BPM)) + } - c.Add(newFormText("Year", true)) - c.Add(newFormText(strconv.Itoa(t.track.Year), false)) + addFormRow(c, "Content type", t.track.ContentType) + addFormRow(c, "Bit rate", fmt.Sprintf("%d kbps", t.track.BitRate)) + addFormRow(c, "File size", util.BytesToSizeString(t.track.Size)) + addFormRow(c, "Play count", strconv.Itoa(t.track.PlayCount)) - c.Add(newFormText("Bit Rate", true)) - c.Add(newFormText(strconv.Itoa(t.track.BitRate)+"kbps", false)) + if t.track.ReplayGain.TrackPeak > 0 { + addFormRow(c, "Track gain", fmt.Sprintf("%0.2f dB", t.track.ReplayGain.TrackGain)) + addFormRow(c, "Track peak", fmt.Sprintf("%0.6f", t.track.ReplayGain.TrackPeak)) + } + if t.track.ReplayGain.AlbumPeak > 0 { + addFormRow(c, "Album gain", fmt.Sprintf("%0.2f dB", t.track.ReplayGain.AlbumGain)) + addFormRow(c, "Album peak", fmt.Sprintf("%0.6f", t.track.ReplayGain.AlbumPeak)) + } - c.Add(newFormText("File Size", true)) - c.Add(newFormText(util.BytesToSizeString(t.track.Size), false)) + title := widget.NewRichTextWithText("Track Info") + title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true + dismissBtn := widget.NewButton("Close", func() { + if t.OnDismiss != nil { + t.OnDismiss() + } + }) - c.Add(newFormText("Play Count", true)) - c.Add(newFormText(strconv.Itoa(t.track.PlayCount), false)) + return widget.NewSimpleRenderer( + container.NewBorder( + /*top*/ container.NewHBox(layout.NewSpacer(), title, layout.NewSpacer()), + /*bottom*/ container.NewVBox( + widget.NewSeparator(), + container.NewHBox(layout.NewSpacer(), dismissBtn), + ), + /*left/right*/ nil, nil, + /*center*/ container.NewScroll(c), + ), + ) +} - return widget.NewSimpleRenderer(c) +func addFormRow(c *fyne.Container, left, right string) { + c.Add(newFormText(left, true)) + c.Add(newFormText(right, false)) } func newFormText(text string, leftCol bool) *widget.RichText { @@ -82,7 +109,7 @@ func newFormText(text string, leftCol bool) *widget.RichText { if leftCol { alignment = fyne.TextAlignTrailing } - return widget.NewRichText( + rt := widget.NewRichText( &widget.TextSegment{ Text: text, Style: widget.RichTextStyle{ @@ -91,4 +118,8 @@ func newFormText(text string, leftCol bool) *widget.RichText { }, }, ) + if !leftCol { + rt.Wrapping = fyne.TextWrapWord + } + return rt } From bf97cdb52680d45155eb7026a757113db48f1ffe Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 18 Jul 2024 17:33:42 -0700 Subject: [PATCH 3/3] add more to track info dialog --- backend/mediaprovider/helpers/other.go | 6 ++- backend/mediaprovider/model.go | 5 ++- .../subsonic/subsonicmediaprovider.go | 13 ++++++- backend/mpris.go | 8 ++-- ui/controller/controller.go | 15 +++++++ ui/dialogs/trackinfodialog.go | 39 ++++++++++++++++++- 6 files changed, 76 insertions(+), 10 deletions(-) diff --git a/backend/mediaprovider/helpers/other.go b/backend/mediaprovider/helpers/other.go index dd70978..41d9682 100644 --- a/backend/mediaprovider/helpers/other.go +++ b/backend/mediaprovider/helpers/other.go @@ -14,7 +14,11 @@ func GetSimilarSongsFallback(mp mediaprovider.MediaProvider, track *mediaprovide tracks, _ = mp.GetSimilarTracks(track.ArtistIDs[0], count) } if len(tracks) == 0 { - tracks, _ = mp.GetRandomTracks(track.Genre, count) + genre := "" + if len(track.Genres) > 0 { + genre = track.Genres[0] + } + tracks, _ = mp.GetRandomTracks(genre, count) } // make sure to exclude the song itself from the similar list diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index 9d80a74..2516dc1 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -1,5 +1,7 @@ package mediaprovider +import "time" + // Bit field flag for the ReleaseTypes property type ReleaseType = int32 @@ -90,7 +92,7 @@ type Track struct { Duration int TrackNumber int DiscNumber int - Genre string + Genres []string ArtistIDs []string ArtistNames []string Album string @@ -100,6 +102,7 @@ type Track struct { Favorite bool Size int64 PlayCount int + LastPlayed time.Time FilePath string BitRate int ContentType string diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index d38f7bb..cd3c681 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -500,6 +500,15 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { rGain.AlbumPeak = rg.AlbumPeak rGain.TrackPeak = rg.TrackPeak } + var genres []string + if len(ch.Genres) > 0 { + genres = sharedutil.MapSlice(ch.Genres, func(idName subsonic.IDName) string { + return idName.Name + }) + } else if ch.Genre != "" { + genres = []string{ch.Genre} + } + return &mediaprovider.Track{ ID: ch.ID, CoverArtID: ch.CoverArt, @@ -508,7 +517,7 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { Duration: ch.Duration, TrackNumber: ch.Track, DiscNumber: ch.DiscNumber, - Genre: ch.Genre, + Genres: genres, ArtistIDs: artistIDs, ArtistNames: artistNames, Album: ch.Album, @@ -517,12 +526,14 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { Rating: ch.UserRating, Favorite: !ch.Starred.IsZero(), PlayCount: int(ch.PlayCount), + LastPlayed: ch.Played, FilePath: ch.Path, Size: ch.Size, BitRate: ch.BitRate, ContentType: ch.ContentType, Comment: ch.Comment, BPM: ch.BPM, + ReplayGain: rGain, } } diff --git a/backend/mpris.go b/backend/mpris.go index 7be7121..d0716c3 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -254,7 +254,7 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) { var meta mediaprovider.MediaItemMetadata // metadata that can come only from tracks var discNumber, trackNumber, userRating, playCount, year int - var genre string + var genres []string if np := m.pm.NowPlaying(); np != nil && status.State != player.Stopped { meta = np.Metadata() @@ -264,7 +264,7 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) { userRating = track.Rating playCount = track.PlayCount year = track.Year - genre = track.Genre + genres = track.Genres } } var artURL string @@ -284,9 +284,7 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) { UserRating: float64(userRating) / 5, UseCount: playCount, ArtUrl: artURL, - } - if genre != "" { - mprisMeta.Genre = []string{genre} + Genre: genres, } if year != 0 { mprisMeta.ContentCreated = strconv.Itoa(year) diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 41affdc..65dd509 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -866,6 +866,21 @@ func (c *Controller) ShowTrackInfoDialog(track *mediaprovider.Track) { pop.Hide() c.doModalClosed() } + info.OnNavigateToAlbum = func(albumID string) { + info.OnDismiss() + c.NavigateTo(AlbumRoute(albumID)) + } + info.OnNavigateToArtist = func(artistID string) { + info.OnDismiss() + c.NavigateTo(ArtistRoute(artistID)) + } + info.OnNavigateToGenre = func(genre string) { + info.OnDismiss() + c.NavigateTo(GenreRoute(genre)) + } + info.OnCopyFilePath = func() { + c.MainWindow.Clipboard().SetContent(track.FilePath) + } c.ClosePopUpOnEscape(pop) winSize := c.MainWindow.Canvas().Size() popMin := pop.MinSize() diff --git a/ui/dialogs/trackinfodialog.go b/ui/dialogs/trackinfodialog.go index 4553ad9..7981bbb 100644 --- a/ui/dialogs/trackinfodialog.go +++ b/ui/dialogs/trackinfodialog.go @@ -3,10 +3,12 @@ package dialogs import ( "fmt" "strconv" + "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/ui/util" @@ -19,6 +21,8 @@ type TrackInfoDialog struct { OnDismiss func() OnNavigateToArtist func(artistID string) OnNavigateToAlbum func(albumID string) + OnNavigateToGenre func(genre string) + OnCopyFilePath func() track *mediaprovider.Track } @@ -53,8 +57,31 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { } c.Add(album) + if len(t.track.Genres) > 0 { + c.Add(newFormText("Genres", true)) + genres := widgets.NewMultiHyperlink() + genres.BuildSegments(t.track.Genres, t.track.Genres) + genres.OnTapped = func(g string) { + if t.OnNavigateToGenre != nil { + t.OnNavigateToGenre(g) + } + } + c.Add(genres) + } + addFormRow(c, "Duration", util.SecondsToTimeString(float64(t.track.Duration))) - addFormRow(c, "File path", t.track.FilePath) + + copyBtn := widgets.NewIconButton(theme.ContentCopyIcon(), func() { + if t.OnCopyFilePath != nil { + t.OnCopyFilePath() + } + }) + copyBtn.IconSize = widgets.IconButtonSizeSmaller + btnCtr := container.New(layout.NewCustomPaddedLayout(8, 0, 10, 0), + container.NewVBox(copyBtn, layout.NewSpacer())) + c.Add(container.NewHBox(btnCtr, newFormText("File path", true))) + c.Add(newFormText(t.track.FilePath, false)) + addFormRow(c, "Comment", t.track.Comment) addFormRow(c, "Year", strconv.Itoa(t.track.Year)) addFormRow(c, "Track number", strconv.Itoa(t.track.TrackNumber)) @@ -69,6 +96,10 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { addFormRow(c, "File size", util.BytesToSizeString(t.track.Size)) addFormRow(c, "Play count", strconv.Itoa(t.track.PlayCount)) + if !t.track.LastPlayed.IsZero() { + addFormRow(c, "Last played", t.track.LastPlayed.Format(time.RFC1123)) + } + if t.track.ReplayGain.TrackPeak > 0 { addFormRow(c, "Track gain", fmt.Sprintf("%0.2f dB", t.track.ReplayGain.TrackGain)) addFormRow(c, "Track peak", fmt.Sprintf("%0.6f", t.track.ReplayGain.TrackPeak)) @@ -94,12 +125,16 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { container.NewHBox(layout.NewSpacer(), dismissBtn), ), /*left/right*/ nil, nil, - /*center*/ container.NewScroll(c), + /*center*/ container.New(layout.NewCustomPaddedLayout(10, 10, 15, 15), + container.NewScroll(c)), ), ) } func addFormRow(c *fyne.Container, left, right string) { + if right == "" { + return + } c.Add(newFormText(left, true)) c.Add(newFormText(right, false)) }