From b0d918b93ead8a0066e38fc885b8abbce2d62406 Mon Sep 17 00:00:00 2001 From: zacaj Date: Tue, 11 Nov 2025 00:03:34 +0000 Subject: [PATCH 1/5] (partial) Fix #742: add Date Added column --- .../jellyfin/jellyfinmediaprovider.go | 2 ++ backend/mediaprovider/model.go | 1 + .../subsonic/subsonicmediaprovider.go | 1 + res/translations/en.json | 5 ++-- ui/dialogs/trackinfodialog.go | 5 ++++ ui/util/util.go | 17 ++++++++++++ ui/widgets/tracklist.go | 2 ++ ui/widgets/tracklistrow.go | 26 ++++++++++++++----- 8 files changed, 49 insertions(+), 10 deletions(-) diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 69eb2f1..f9d99d0 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -435,6 +435,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track { } lastPlayed, _ := time.Parse(time.RFC3339Nano, ch.UserData.LastPlayedDate) + dateAdded, _ := time.Parse(time.RFC3339Nano, ch.DateCreated) t := &mediaprovider.Track{ ID: ch.Id, @@ -454,6 +455,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track { Favorite: ch.UserData.IsFavorite, PlayCount: ch.UserData.PlayCount, LastPlayed: lastPlayed, + DateAdded: dateAdded, } if len(ch.MediaSources) > 0 { t.FilePath = ch.MediaSources[0].Path diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index 809e9ec..45cbacb 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -152,6 +152,7 @@ type Track struct { SampleRate int BitDepth int Channels int + DateAdded time.Time } type ReplayGainInfo struct { diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 074ae5b..049bb68 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -589,6 +589,7 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { Favorite: !ch.Starred.IsZero(), PlayCount: int(ch.PlayCount), LastPlayed: ch.Played, + DateAdded: ch.Created, FilePath: ch.Path, Size: ch.Size, BitRate: ch.BitRate, diff --git a/res/translations/en.json b/res/translations/en.json index a3ded54..48cf7da 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -62,6 +62,7 @@ "Content type": "Content type", "Could not reach server": "Could not reach server", "Create new playlist": "Create new playlist", + "Date added": "Date added", "day": "day", "days": "days", "Delete Playlist": "Delete Playlist", @@ -290,12 +291,10 @@ "Oct": "Oct", "Nov": "Nov", "Dec": "Dec", - "playlist.addedtracks": { "one": "Added one track to playlist", "other": "Added {{.trackCount}} tracks to playlist" }, - "x_minutes_ago": { "one": "a minute ago", "other": "{{.minutes}} minutes ago" @@ -316,4 +315,4 @@ "one": "a year ago", "other": "{{.years}} years ago" } -} +} \ No newline at end of file diff --git a/ui/dialogs/trackinfodialog.go b/ui/dialogs/trackinfodialog.go index 3002522..8600f60 100644 --- a/ui/dialogs/trackinfodialog.go +++ b/ui/dialogs/trackinfodialog.go @@ -115,6 +115,11 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { addFormRow(c, lang.L("Channels"), strconv.Itoa(t.track.Channels)) } addFormRow(c, lang.L("File size"), util.BytesToSizeString(t.track.Size)) + + if !t.track.DateAdded.IsZero() { + addFormRow(c, lang.L("Date added"), t.track.DateAdded.Format(time.RFC1123)) + } + addFormRow(c, lang.L("Play count"), strconv.Itoa(t.track.PlayCount)) if !t.track.LastPlayed.IsZero() { diff --git a/ui/util/util.go b/ui/util/util.go index a77f915..2824c34 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -100,6 +100,23 @@ func FormatItemDate(date mediaprovider.ItemDate) string { return sb.String() } +func FormatDate(t time.Time) string { + if t.IsZero() { + return "" + } + + df := dateFormatForLocale(fyne.CurrentDevice().Locale().String()) + switch df { + case DateFormatDMY: + return t.Format("2 Jan 2006") + case DateFormatMDY: + return t.Format("Jan 2 2006") + case DateFormatYMD: + return t.Format("2006 Jan 2") + } + return "" +} + func LastPlayedDisplayString(t time.Time) string { if t.IsZero() { return lang.L("never") diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index f792e3b..b8bae4b 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -461,6 +461,8 @@ func (t *Tracklist) doSortTracks() { } return 0 }) + case ColumnDateAdded: + t.intSort(func(tr *util.TrackListModel) int64 { return tr.Track().DateAdded.Unix() }) } } diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index bf58318..f64eba8 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -66,6 +66,7 @@ const ( ColumnBPM = "BPM" ColumnBitrate = "Bitrate" ColumnSize = "Size" + ColumnDateAdded = "DateAdded" ColumnPath = "Path" ) @@ -93,6 +94,7 @@ var ( bpm := lang.L("BPM") bitrate := lang.L("Bit rate") size := lang.L("Size") + dateAdded := lang.L("Date added") filepath := lang.L("File path") CompactTracklistRowColumns = []TracklistColumn{ @@ -111,6 +113,7 @@ var ( {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnDateAdded, Col: ListColumn{Text: dateAdded, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, } @@ -129,6 +132,7 @@ var ( {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnDateAdded, Col: ListColumn{Text: dateAdded, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, } @@ -152,6 +156,10 @@ var ( widget.NewLabel(lang.LocalizePluralKey("x_minutes_ago", "59 minutes ago", 59, map[string]string{"minutes": "59"})).MinSize().Width, widget.NewLabel(lastPlayed).MinSize().Width+sortIconWidth, ) + dateAddedColWidth := fyne.Max( + widget.NewLabel("2006 Jan 30").MinSize().Width, + widget.NewLabel(dateAdded).MinSize().Width+sortIconWidth, + ) bpmColWidth := fyne.Max( widget.NewLabel(bpm+" ").MinSize().Width, widget.NewLabel("9999").MinSize().Width) @@ -162,10 +170,10 @@ var ( widget.NewLabel("99.9 MB").MinSize().Width, widget.NewLabel(size).MinSize().Width+sortIconWidth) - // #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, Path - CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, -1} - // #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, Path - ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, -1} + // #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, DateAdded, Path + CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, dateAddedColWidth, -1} + // #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, DateAdded, Path + ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, dateAddedColWidth, -1} }) ) @@ -208,6 +216,7 @@ type tracklistRowBase struct { comment *ttwidget.Label bpm *widget.Label size *widget.Label + dateAdded *widget.Label path *ttwidget.Label // must be injected by extending widget @@ -262,7 +271,7 @@ func NewExpandedTracklistRow(tracklist *Tracklist, im *backend.ImageManager, pla v := makeVerticallyCentered // func alias container := container.New(tracklist.colLayout, - v(t.num), titleArtistImg, v(t.album), v(t.composer), v(t.dur), v(t.year), v(t.favorite), v(t.rating), v(t.plays), v(t.lastPlayed), v(t.comment), v(t.bpm), v(t.bitrate), v(t.size), v(t.path)) + v(t.num), titleArtistImg, v(t.album), v(t.composer), v(t.dur), v(t.year), v(t.favorite), v(t.rating), v(t.plays), v(t.lastPlayed), v(t.comment), v(t.bpm), v(t.bitrate), v(t.size), v(t.dateAdded), v(t.path)) t.Content = container t.setColVisibility = func(colNum int, vis bool) bool { c := container.Objects[colNum].(*fyne.Container) @@ -290,7 +299,7 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) t.playingIcon = playingIcon t.Content = container.New(tracklist.colLayout, - t.num, t.name, t.artist, t.album, t.composer, t.dur, t.year, t.favorite, t.rating, t.plays, t.lastPlayed, t.comment, t.bpm, t.bitrate, t.size, t.path) + t.num, t.name, t.artist, t.album, t.composer, t.dur, t.year, t.favorite, t.rating, t.plays, t.lastPlayed, t.comment, t.bpm, t.bitrate, t.size, t.dateAdded, t.path) colHiddenPtrMap := map[int]*bool{ 2: &t.artist.Hidden, @@ -306,7 +315,8 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) 12: &t.bpm.Hidden, 13: &t.bitrate.Hidden, 14: &t.size.Hidden, - 15: &t.path.Hidden, + 15: &t.dateAdded.Hidden, + 16: &t.path.Hidden, } t.setColVisibility = func(colNum int, vis bool) bool { ptr, ok := colHiddenPtrMap[colNum] @@ -355,6 +365,7 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.bpm = util.NewTrailingAlignLabel() t.bitrate = util.NewTrailingAlignLabel() t.size = util.NewTrailingAlignLabel() + t.dateAdded = util.NewTruncatingLabel() t.path = util.NewTruncatingTooltipLabel() t.path.OnMouseIn = t.MouseIn t.path.OnMouseOut = t.MouseOut @@ -422,6 +433,7 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) { t.bpm.Text = strconv.Itoa(tr.BPM) t.bitrate.Text = strconv.Itoa(tr.BitRate) t.size.Text = util.BytesToSizeString(tr.Size) + t.dateAdded.Text = util.FormatDate(tr.DateAdded) t.path.Text = tr.FilePath t.path.SetToolTip(tr.FilePath) changed = true From 7058f74d4204bc33965c58e0771e5754335c63b3 Mon Sep 17 00:00:00 2001 From: zacaj Date: Tue, 11 Nov 2025 01:13:38 +0000 Subject: [PATCH 2/5] (partial) Fix #742: add Genre, Album Artist, File Type columns --- backend/mediaprovider/model.go | 63 ++++++----- .../subsonic/subsonicmediaprovider.go | 72 +++++++----- res/translations/en.json | 3 + ui/controller/connectactions.go | 3 + ui/dialogs/trackinfodialog.go | 13 +++ ui/widgets/tracklist.go | 13 +++ ui/widgets/tracklistrow.go | 105 ++++++++++++------ 7 files changed, 176 insertions(+), 96 deletions(-) diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index 45cbacb..df08a0d 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -123,36 +123,39 @@ type Genre struct { } type Track struct { - ID string - CoverArtID string - ParentID string - Title string - Duration time.Duration - TrackNumber int - DiscNumber int - Genres []string - ArtistIDs []string - ArtistNames []string - ComposerIDs []string - ComposerNames []string - Album string - AlbumID string - Year int - Rating int - Favorite bool - Size int64 - PlayCount int - LastPlayed time.Time - FilePath string - BitRate int - ContentType string - Comment string - BPM int - ReplayGain ReplayGainInfo - SampleRate int - BitDepth int - Channels int - DateAdded time.Time + ID string + CoverArtID string + ParentID string + Title string + Duration time.Duration + TrackNumber int + DiscNumber int + Genres []string + ArtistIDs []string + ArtistNames []string + AlbumArtistIDs []string + AlbumArtistNames []string + ComposerIDs []string + ComposerNames []string + Album string + AlbumID string + Year int + Rating int + Favorite bool + Size int64 + PlayCount int + LastPlayed time.Time + FilePath string + BitRate int + ContentType string + Comment string + BPM int + ReplayGain ReplayGainInfo + SampleRate int + BitDepth int + Extension string + Channels int + DateAdded time.Time } type ReplayGainInfo struct { diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 049bb68..b7c9c61 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -544,6 +544,15 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { artistIDs = append(artistIDs, ch.ArtistID) } + var albumArtistNames, albumArtistIDs []string + if len(ch.AlbumArtists) > 0 { + // OpenSubsonic extension + for _, a := range ch.AlbumArtists { + albumArtistIDs = append(albumArtistIDs, a.ID) + albumArtistNames = append(albumArtistNames, a.Name) + } + } + var rGain mediaprovider.ReplayGainInfo if rg := ch.ReplayGain; rg != nil { rGain.AlbumGain = rg.AlbumGain @@ -570,36 +579,39 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { } return &mediaprovider.Track{ - ID: ch.ID, - CoverArtID: ch.CoverArt, - ParentID: ch.Parent, - Title: ch.Title, - Duration: time.Duration(ch.Duration) * time.Second, - TrackNumber: ch.Track, - DiscNumber: ch.DiscNumber, - Genres: genres, - ArtistIDs: artistIDs, - ArtistNames: artistNames, - ComposerIDs: composerIDs, - ComposerNames: composers, - Album: ch.Album, - AlbumID: ch.AlbumID, - Year: ch.Year, - Rating: ch.UserRating, - Favorite: !ch.Starred.IsZero(), - PlayCount: int(ch.PlayCount), - LastPlayed: ch.Played, - DateAdded: ch.Created, - FilePath: ch.Path, - Size: ch.Size, - BitRate: ch.BitRate, - ContentType: ch.ContentType, - Comment: ch.Comment, - BPM: ch.BPM, - ReplayGain: rGain, - SampleRate: ch.SamplingRate, - BitDepth: ch.BitDepth, - Channels: ch.ChannelCount, + ID: ch.ID, + CoverArtID: ch.CoverArt, + ParentID: ch.Parent, + Title: ch.Title, + Duration: time.Duration(ch.Duration) * time.Second, + TrackNumber: ch.Track, + DiscNumber: ch.DiscNumber, + Genres: genres, + ArtistIDs: artistIDs, + ArtistNames: artistNames, + AlbumArtistIDs: albumArtistIDs, + AlbumArtistNames: albumArtistNames, + ComposerIDs: composerIDs, + ComposerNames: composers, + Album: ch.Album, + AlbumID: ch.AlbumID, + Year: ch.Year, + Rating: ch.UserRating, + Favorite: !ch.Starred.IsZero(), + PlayCount: int(ch.PlayCount), + LastPlayed: ch.Played, + DateAdded: ch.Created, + FilePath: ch.Path, + Size: ch.Size, + BitRate: ch.BitRate, + ContentType: ch.ContentType, + Extension: ch.Suffix, + Comment: ch.Comment, + BPM: ch.BPM, + ReplayGain: rGain, + SampleRate: ch.SamplingRate, + BitDepth: ch.BitDepth, + Channels: ch.ChannelCount, } } diff --git a/res/translations/en.json b/res/translations/en.json index 48cf7da..cda92fe 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -6,6 +6,8 @@ "Add to queue": "Add to queue", "Album": "Album", "album": "album", + "Album artist": "Album artist", + "Album artists": "Album artists", "Album count": "Album count", "Album Count": "Album Count", "Album filters": "Album filters", @@ -95,6 +97,7 @@ "Field Recording": "Field Recording", "File path": "File path", "File size": "File size", + "File type": "File type", "Filter albums": "Filter albums", "Filter genres": "Filter genres", "Forward": "Forward", diff --git a/ui/controller/connectactions.go b/ui/controller/connectactions.go index 18b561c..1f051e5 100644 --- a/ui/controller/connectactions.go +++ b/ui/controller/connectactions.go @@ -52,6 +52,9 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget tracklist.OnShowArtistPage = func(artistID string) { m.NavigateTo(ArtistRoute(artistID)) } + tracklist.OnShowGenrePage = func(genre string) { + m.NavigateTo(GenreRoute(genre)) + } tracklist.OnColumnVisibilityMenuShown = func(pop *widget.PopUp) { m.ClosePopUpOnEscape(pop) } diff --git a/ui/dialogs/trackinfodialog.go b/ui/dialogs/trackinfodialog.go index 8600f60..f51239c 100644 --- a/ui/dialogs/trackinfodialog.go +++ b/ui/dialogs/trackinfodialog.go @@ -58,6 +58,18 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { } c.Add(artists) + if len(t.track.AlbumArtistNames) > 0 { + c.Add(newFormText(lang.L("Album artists"), true)) + albumArtists := widgets.NewMultiHyperlink() + albumArtists.BuildSegments(t.track.AlbumArtistNames, t.track.AlbumArtistIDs) + albumArtists.OnTapped = func(id string) { + if t.OnNavigateToArtist != nil { + t.OnNavigateToArtist(id) + } + } + c.Add(albumArtists) + } + if len(t.track.ComposerNames) > 0 { c.Add(newFormText(lang.L("Composers"), true)) composers := widgets.NewMultiHyperlink() @@ -104,6 +116,7 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer { c.Add(newFormText(t.track.FilePath, false)) addFormRow(c, lang.L("Content type"), t.track.ContentType) + addFormRow(c, lang.L("File type"), t.track.Extension) addFormRow(c, lang.L("Bit rate"), fmt.Sprintf("%d kbps", t.track.BitRate)) if t.track.SampleRate > 0 { addFormRow(c, lang.L("Sample rate"), fmt.Sprintf("%d Hz", t.track.SampleRate)) diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index b8bae4b..52946e8 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -79,6 +79,7 @@ type Tracklist struct { OnShowArtistPage func(artistID string) OnShowAlbumPage func(albumID string) + OnShowGenrePage func(genre string) OnColumnVisibilityMenuShown func(*widget.PopUp) OnVisibleColumnsChanged func([]string) @@ -432,8 +433,12 @@ func (t *Tracklist) doSortTracks() { t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().Title }) case ColumnArtist: t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().ArtistNames, ", ") }) + case ColumnAlbumArtist: + t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().ArtistNames, ", ") }) case ColumnAlbum: t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().Album }) + case ColumnGenre: + t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().Genres, ", ") }) case ColumnPath: t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().FilePath }) case ColumnRating: @@ -450,6 +455,8 @@ func (t *Tracklist) doSortTracks() { t.intSort(func(tr *util.TrackListModel) int64 { return tr.Track().LastPlayed.Unix() }) case ColumnComment: t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().Comment }) + case ColumnFileType: + t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().Extension }) case ColumnBPM: t.intSort(func(tr *util.TrackListModel) int64 { return int64(tr.Track().BPM) }) case ColumnBitrate: @@ -599,6 +606,12 @@ func (t *Tracklist) onAlbumTapped(albumID string) { } } +func (t *Tracklist) onGenreTapped(genre string) { + if t.OnShowGenrePage != nil { + t.OnShowGenrePage(genre) + } +} + func (t *Tracklist) onDownload(tracks []*mediaprovider.Track, downloadName string) { if t.OnDownload != nil { t.OnDownload(tracks, downloadName) diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index f64eba8..8d2ea0d 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -53,9 +53,11 @@ const ( ColumnNum = "Num" ColumnTitle = "Title" ColumnArtist = "Artist" + ColumnAlbumArtist = "AlbumArtist" ColumnTitleArtist = "Title/Artist" ColumnAlbum = "Album" ColumnComposer = "Composer" + ColumnGenre = "Genre" ColumnTime = "Time" ColumnYear = "Year" ColumnFavorite = "Favorite" @@ -66,6 +68,7 @@ const ( ColumnBPM = "BPM" ColumnBitrate = "Bitrate" ColumnSize = "Size" + ColumnFileType = "FileType" ColumnDateAdded = "DateAdded" ColumnPath = "Path" ) @@ -83,7 +86,9 @@ var ( title := lang.L("Title") artist := lang.L("Artist") album := lang.L("Album") + albumArtist := lang.L("Album artist") composer := lang.L("Composer") + genre := lang.L("Genre") time := lang.L("Time") year := lang.L("Year") fav := lang.L("Fav.") @@ -94,6 +99,7 @@ var ( bpm := lang.L("BPM") bitrate := lang.L("Bit rate") size := lang.L("Size") + fileType := lang.L("File type") dateAdded := lang.L("Date added") filepath := lang.L("File path") @@ -102,7 +108,9 @@ var ( {Name: ColumnTitle, Col: ListColumn{Text: title, Alignment: fyne.TextAlignLeading, CanToggleVisible: false}}, {Name: ColumnArtist, Col: ListColumn{Text: artist, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnAlbum, Col: ListColumn{Text: album, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, + {Name: ColumnAlbumArtist, Col: ListColumn{Text: albumArtist, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnComposer, Col: ListColumn{Text: composer, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, + {Name: ColumnGenre, Col: ListColumn{Text: genre, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnTime, Col: ListColumn{Text: time, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnYear, Col: ListColumn{Text: year, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnFavorite, Col: ListColumn{Text: " " + fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}}, @@ -113,6 +121,7 @@ var ( {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnFileType, Col: ListColumn{Text: fileType, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnDateAdded, Col: ListColumn{Text: dateAdded, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, } @@ -121,7 +130,9 @@ var ( {Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}, {Name: ColumnTitleArtist, Col: ListColumn{Text: title + " / " + artist, Alignment: fyne.TextAlignLeading, CanToggleVisible: false}}, {Name: ColumnAlbum, Col: ListColumn{Text: album, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, + {Name: ColumnAlbumArtist, Col: ListColumn{Text: albumArtist, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnComposer, Col: ListColumn{Text: composer, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, + {Name: ColumnGenre, Col: ListColumn{Text: genre, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnTime, Col: ListColumn{Text: time, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnYear, Col: ListColumn{Text: year, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnFavorite, Col: ListColumn{Text: fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}}, @@ -132,6 +143,7 @@ var ( {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnFileType, Col: ListColumn{Text: fileType, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnDateAdded, Col: ListColumn{Text: dateAdded, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, } @@ -169,11 +181,14 @@ var ( sizeColWidth := fyne.Max( widget.NewLabel("99.9 MB").MinSize().Width, widget.NewLabel(size).MinSize().Width+sortIconWidth) + fileTypeColWidth := fyne.Max( + widget.NewLabel("FLAC").MinSize().Width, + widget.NewLabel(size).MinSize().Width+sortIconWidth) - // #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, DateAdded, Path - CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, dateAddedColWidth, -1} - // #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, DateAdded, Path - ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, dateAddedColWidth, -1} + // #, Title, Artist, Album, AlbumArtist, Composer, Genre, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, FileType, DateAdded, Path + CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, fileTypeColWidth, dateAddedColWidth, -1} + // #, Title/Artist, Album, AlbumArtist, Composer, Genre, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, FileType, DateAdded, Path + ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, fileTypeColWidth, dateAddedColWidth, -1} }) ) @@ -201,23 +216,26 @@ type tracklistRowBase struct { nextUpdateModel *util.TrackListModel nextUpdateRowNum int - num *widget.Label - name *ttwidget.RichText - artist *MultiHyperlink - album *MultiHyperlink // for disabled support, if albumID is "" - composer *MultiHyperlink - dur *widget.Label - year *widget.Label - favorite *fyne.Container - rating *StarRating - bitrate *widget.Label - plays *widget.Label - lastPlayed *widget.Label - comment *ttwidget.Label - bpm *widget.Label - size *widget.Label - dateAdded *widget.Label - path *ttwidget.Label + num *widget.Label + name *ttwidget.RichText + artist *MultiHyperlink + album *MultiHyperlink // for disabled support, if albumID is "" + albumArtist *MultiHyperlink + composer *MultiHyperlink + genre *MultiHyperlink + dur *widget.Label + year *widget.Label + favorite *fyne.Container + rating *StarRating + bitrate *widget.Label + plays *widget.Label + lastPlayed *widget.Label + comment *ttwidget.Label + bpm *widget.Label + size *widget.Label + fileType *widget.Label + dateAdded *widget.Label + path *ttwidget.Label // must be injected by extending widget setColVisibility func(int, bool) bool @@ -271,7 +289,7 @@ func NewExpandedTracklistRow(tracklist *Tracklist, im *backend.ImageManager, pla v := makeVerticallyCentered // func alias container := container.New(tracklist.colLayout, - v(t.num), titleArtistImg, v(t.album), v(t.composer), v(t.dur), v(t.year), v(t.favorite), v(t.rating), v(t.plays), v(t.lastPlayed), v(t.comment), v(t.bpm), v(t.bitrate), v(t.size), v(t.dateAdded), v(t.path)) + v(t.num), titleArtistImg, v(t.album), v(t.albumArtist), v(t.composer), v(t.genre), v(t.dur), v(t.year), v(t.favorite), v(t.rating), v(t.plays), v(t.lastPlayed), v(t.comment), v(t.bpm), v(t.bitrate), v(t.size), v(t.fileType), v(t.dateAdded), v(t.path)) t.Content = container t.setColVisibility = func(colNum int, vis bool) bool { c := container.Objects[colNum].(*fyne.Container) @@ -299,24 +317,27 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) t.playingIcon = playingIcon t.Content = container.New(tracklist.colLayout, - t.num, t.name, t.artist, t.album, t.composer, t.dur, t.year, t.favorite, t.rating, t.plays, t.lastPlayed, t.comment, t.bpm, t.bitrate, t.size, t.dateAdded, t.path) + t.num, t.name, t.artist, t.album, t.albumArtist, t.composer, t.genre, t.dur, t.year, t.favorite, t.rating, t.plays, t.lastPlayed, t.comment, t.bpm, t.bitrate, t.size, t.fileType, t.dateAdded, t.path) colHiddenPtrMap := map[int]*bool{ 2: &t.artist.Hidden, 3: &t.album.Hidden, - 4: &t.composer.Hidden, - 5: &t.dur.Hidden, - 6: &t.year.Hidden, - 7: &t.favorite.Hidden, - 8: &t.rating.Hidden, - 9: &t.plays.Hidden, - 10: &t.lastPlayed.Hidden, - 11: &t.comment.Hidden, - 12: &t.bpm.Hidden, - 13: &t.bitrate.Hidden, - 14: &t.size.Hidden, - 15: &t.dateAdded.Hidden, - 16: &t.path.Hidden, + 4: &t.albumArtist.Hidden, + 5: &t.composer.Hidden, + 6: &t.genre.Hidden, + 7: &t.dur.Hidden, + 8: &t.year.Hidden, + 9: &t.favorite.Hidden, + 10: &t.rating.Hidden, + 11: &t.plays.Hidden, + 12: &t.lastPlayed.Hidden, + 13: &t.comment.Hidden, + 14: &t.bpm.Hidden, + 15: &t.bitrate.Hidden, + 16: &t.size.Hidden, + 17: &t.fileType.Hidden, + 18: &t.dateAdded.Hidden, + 19: &t.path.Hidden, } t.setColVisibility = func(colNum int, vis bool) bool { ptr, ok := colHiddenPtrMap[colNum] @@ -344,10 +365,18 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) } t.album.OnMouseIn = t.MouseIn t.album.OnMouseOut = t.MouseOut + t.albumArtist = NewMultiHyperlink() + t.albumArtist.OnTapped = tracklist.onArtistTapped + t.albumArtist.OnMouseIn = t.MouseIn + t.albumArtist.OnMouseOut = t.MouseOut t.composer = NewMultiHyperlink() t.composer.OnTapped = func(id string) { tracklist.onArtistTapped(id) } t.composer.OnMouseIn = t.MouseIn t.composer.OnMouseOut = t.MouseOut + t.genre = NewMultiHyperlink() + t.genre.OnTapped = func(id string) { tracklist.onGenreTapped(id) } + t.genre.OnMouseIn = t.MouseIn + t.genre.OnMouseOut = t.MouseOut t.dur = util.NewTrailingAlignLabel() t.year = util.NewTrailingAlignLabel() favorite := NewFavoriteIcon() @@ -365,6 +394,7 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.bpm = util.NewTrailingAlignLabel() t.bitrate = util.NewTrailingAlignLabel() t.size = util.NewTrailingAlignLabel() + t.fileType = util.NewTrailingAlignLabel() t.dateAdded = util.NewTruncatingLabel() t.path = util.NewTruncatingTooltipLabel() t.path.OnMouseIn = t.MouseIn @@ -423,7 +453,9 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) { t.name.SetToolTip(tr.Title) t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs) t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID}) + t.albumArtist.BuildSegments(tr.AlbumArtistNames, tr.AlbumArtistIDs) t.composer.BuildSegments(tr.ComposerNames, tr.ComposerIDs) + t.genre.BuildSegments(tr.Genres, tr.Genres) t.dur.Text = util.SecondsToMMSS(tr.Duration.Seconds()) t.year.Text = strconv.Itoa(tr.Year) t.plays.Text = strconv.Itoa(int(tr.PlayCount)) @@ -433,6 +465,7 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) { t.bpm.Text = strconv.Itoa(tr.BPM) t.bitrate.Text = strconv.Itoa(tr.BitRate) t.size.Text = util.BytesToSizeString(tr.Size) + t.fileType.Text = tr.Extension t.dateAdded.Text = util.FormatDate(tr.DateAdded) t.path.Text = tr.FilePath t.path.SetToolTip(tr.FilePath) From 1657c978e7c955f0d5a3a329635768f6ca8fddc2 Mon Sep 17 00:00:00 2001 From: zacaj Date: Wed, 12 Nov 2025 23:26:18 +0000 Subject: [PATCH 3/5] fix AlbumArtist sort --- ui/widgets/tracklist.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 52946e8..ea84323 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -434,7 +434,7 @@ func (t *Tracklist) doSortTracks() { case ColumnArtist: t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().ArtistNames, ", ") }) case ColumnAlbumArtist: - t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().ArtistNames, ", ") }) + t.stringSort(func(tr *util.TrackListModel) string { return strings.Join(tr.Track().AlbumArtistNames, ", ") }) case ColumnAlbum: t.stringSort(func(tr *util.TrackListModel) string { return tr.Track().Album }) case ColumnGenre: From 923dad99419f0d8e14adb1d7cf10d35ffc50ea73 Mon Sep 17 00:00:00 2001 From: zacaj Date: Wed, 12 Nov 2025 23:43:36 +0000 Subject: [PATCH 4/5] use FormatitemDate for FormatDate --- ui/util/util.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ui/util/util.go b/ui/util/util.go index 2824c34..9891499 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -105,16 +105,18 @@ func FormatDate(t time.Time) string { return "" } - df := dateFormatForLocale(fyne.CurrentDevice().Locale().String()) - switch df { - case DateFormatDMY: - return t.Format("2 Jan 2006") - case DateFormatMDY: - return t.Format("Jan 2 2006") - case DateFormatYMD: - return t.Format("2006 Jan 2") + v := []int{ + t.Local().Year(), + int(t.Local().Month()), + t.Local().Day(), } - return "" + id := mediaprovider.ItemDate{ + Year: &v[0], + Month: &v[1], + Day: &v[2], + } + + return FormatItemDate(id) } func LastPlayedDisplayString(t time.Time) string { From 88f426d680addf5efcf845c5607f2caa6189d02f Mon Sep 17 00:00:00 2001 From: zacaj Date: Wed, 12 Nov 2025 23:53:56 +0000 Subject: [PATCH 5/5] fix fileType column width --- ui/widgets/tracklistrow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index 8d2ea0d..e4dd0be 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -183,7 +183,7 @@ var ( widget.NewLabel(size).MinSize().Width+sortIconWidth) fileTypeColWidth := fyne.Max( widget.NewLabel("FLAC").MinSize().Width, - widget.NewLabel(size).MinSize().Width+sortIconWidth) + widget.NewLabel(fileType).MinSize().Width+sortIconWidth) // #, Title, Artist, Album, AlbumArtist, Composer, Genre, Time, Year, Favorite, Rating, Plays, LastPlayed, Comment, BPM, Bitrate, Size, FileType, DateAdded, Path CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, lastPlayedColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, fileTypeColWidth, dateAddedColWidth, -1}