From 534a7bfaafd1f2c8db584923617333ad15fdd1a0 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 17 Sep 2025 22:28:16 +0200 Subject: [PATCH 1/7] Run modernise on the project This will be available in "go fix" within Go 1.26 but in the meantime: go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./... --- backend/imagemanager.go | 4 ++-- backend/ipc/server.go | 2 +- .../mediaprovider/jellyfin/jellyfinmediaprovider.go | 2 +- .../mediaprovider/subsonic/subsonicmediaprovider.go | 2 +- backend/playbackmanager.go | 2 +- backend/player/dlna/dlnaplayer.go | 12 ++++++------ backend/waveformimage.go | 6 +++--- ui/browsing/albumspage.go | 5 +---- ui/browsing/artistspage.go | 5 +---- ui/dialogs/settingsdialog.go | 5 +---- ui/layouts/columnslayout.go | 4 ++-- ui/theme/theme.go | 5 +---- ui/widgets/albumfilterbutton.go | 4 ++-- ui/widgets/starrating.go | 4 ++-- ui/widgets/tracklistloader.go | 2 +- 15 files changed, 26 insertions(+), 38 deletions(-) diff --git a/backend/imagemanager.go b/backend/imagemanager.go index 5f99b5c..af05663 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -46,7 +46,7 @@ type ImageManager struct { maxOnDiskCacheSizeBytes int64 filesWrittenSinceLastPrune bool - serverFetchSema chan interface{} + serverFetchSema chan any } // NewImageManager returns a new ImageManager. @@ -64,7 +64,7 @@ func NewImageManager(ctx context.Context, s *ServerManager, baseCacheDir string) DefaultTTL: 1 * time.Minute, }, maxOnDiskCacheSizeBytes: defaultDiskCacheSizeBytes, - serverFetchSema: make(chan interface{}, maxConcurrentServerFetches), + serverFetchSema: make(chan any, maxConcurrentServerFetches), } s.OnLogout(func() { i.thumbnailCache.Clear() diff --git a/backend/ipc/server.go b/backend/ipc/server.go index d5dc662..77a663b 100644 --- a/backend/ipc/server.go +++ b/backend/ipc/server.go @@ -144,7 +144,7 @@ func (s *serverImpl) createHandler() http.Handler { search = strings.ToLower(search) filtered := make([]mediaprovider.Playlist, 0) - for i := 0; i < len(all); i++ { + for i := range all { playlist := all[i] name := strings.ReplaceAll(playlist.Name, " ", "") name = strings.ToLower(name) diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 4e47a11..7b5fe6f 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -344,7 +344,7 @@ func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteP } numBatches := int(math.Ceil(float64(len(allIDs)) / float64(batchSize))) - for i := 0; i < numBatches; i++ { + for i := range numBatches { var wg sync.WaitGroup batchSetFavorite(i*batchSize, &wg) wg.Wait() diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 478ac5a..ea04a31 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -342,7 +342,7 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar } numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize))) - for i := 0; i < numBatches; i++ { + for i := range numBatches { var wg sync.WaitGroup batchSetRating(i*batchSize, &wg) wg.Wait() diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 09cb9cc..38e54f3 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -498,7 +498,7 @@ func (p *PlaybackManager) PlayRandomAlbums(genreName string) error { } iter := mp.IterateAlbums(mediaprovider.AlbumSortRandom, mediaprovider.NewAlbumFilter(options)) insertMode := Replace - for i := 0; i < 20; i++ { + for i := range 20 { al := iter.Next() if al == nil { break diff --git a/backend/player/dlna/dlnaplayer.go b/backend/player/dlna/dlnaplayer.go index 399f400..9f0ecb0 100644 --- a/backend/player/dlna/dlnaplayer.go +++ b/backend/player/dlna/dlnaplayer.go @@ -618,7 +618,7 @@ func (d *DLNAPlayer) lookupProxyURL(key string) (string, bool) { d.proxyURLLock.Lock() defer d.proxyURLLock.Unlock() - for i := 0; i < len(d.proxyURLs); i++ { + for i := range len(d.proxyURLs) { if d.proxyURLs[i].key == key { url := d.proxyURLs[i].url // Move accessed entry to the most recent position @@ -632,7 +632,7 @@ func (d *DLNAPlayer) lookupProxyURL(key string) (string, bool) { func (d *DLNAPlayer) _updateProxyURL(key, url string) { // Check if the key already exists, and if so, move it to the most recently used position - for i := 0; i < len(d.proxyURLs); i++ { + for i := range len(d.proxyURLs) { if d.proxyURLs[i].key == key { if i < len(d.proxyURLs)-1 { // Shift elements to the left from found position to the end @@ -652,19 +652,19 @@ func (d *DLNAPlayer) _updateProxyURL(key, url string) { type retryLogger struct{} -func (retryLogger) Error(msg string, keysAndValues ...interface{}) { +func (retryLogger) Error(msg string, keysAndValues ...any) { log.Println(msg, keysAndValues) } -func (retryLogger) Info(msg string, keysAndValues ...interface{}) { +func (retryLogger) Info(msg string, keysAndValues ...any) { log.Println(msg, keysAndValues) } -func (retryLogger) Warn(msg string, keysAndValues ...interface{}) { +func (retryLogger) Warn(msg string, keysAndValues ...any) { log.Println(msg, keysAndValues) } -func (retryLogger) Debug(msg string, keysAndValues ...interface{}) { +func (retryLogger) Debug(msg string, keysAndValues ...any) { // log only retries, not every request if strings.Contains(msg, "retrying request") { log.Println(msg, keysAndValues) diff --git a/backend/waveformimage.go b/backend/waveformimage.go index e065e0f..117155b 100644 --- a/backend/waveformimage.go +++ b/backend/waveformimage.go @@ -86,7 +86,7 @@ func (w *WaveformImageJob) Get() *WaveformImage { result := NewWaveformImage() // Copy each scanline from w.img to result - for y := 0; y < height; y++ { + for y := range height { srcOffset := w.img.PixOffset(0, y) dstOffset := result.PixOffset(0, y) copy(result.Pix[dstOffset:dstOffset+w.progress*4], w.img.Pix[srcOffset:srcOffset+w.progress*4]) @@ -226,7 +226,7 @@ func generateWaveformImage(ctx context.Context, data *waveformData, job *Wavefor opaqueColor := color.NRGBA{R: 255, G: 255, B: 255, A: 255} translucentColor := color.NRGBA{R: 255, G: 255, B: 255, A: 128} - for x := 0; x < 1024; x++ { + for x := range 1024 { for data.progress <= x { if data.done { return @@ -341,7 +341,7 @@ func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformDat } // Process samples - for i := 0; i < n; i++ { + for i := range n { sample := float64(buf.Data[i]) / float64(1<<15) // Normalize to [-1, 1] chunkSamples = append(chunkSamples, sample) diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 4c8dc6b..48e9df4 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -62,10 +62,7 @@ func (a *albumsPageAdapter) Route() controller.Route { return controller.AlbumsR func (a *albumsPageAdapter) SortOrders() ([]string, int) { orders := a.mp.AlbumSortOrders() - sortOrder := slices.Index(orders, a.cfg.SortOrder) - if sortOrder < 0 { - sortOrder = 0 - } + sortOrder := max(slices.Index(orders, a.cfg.SortOrder), 0) return util.LocalizeSlice(orders), sortOrder } diff --git a/ui/browsing/artistspage.go b/ui/browsing/artistspage.go index e4b3e51..fca01eb 100644 --- a/ui/browsing/artistspage.go +++ b/ui/browsing/artistspage.go @@ -47,10 +47,7 @@ func (a *artistsPageAdapter) Route() controller.Route { return controller.Artist func (a *artistsPageAdapter) SortOrders() ([]string, int) { orders := a.mp.ArtistSortOrders() - sortOrder := slices.Index(orders, a.cfg.SortOrder) - if sortOrder < 0 { - sortOrder = 0 - } + sortOrder := max(slices.Index(orders, a.cfg.SortOrder), 0) return util.LocalizeSlice(orders), sortOrder } diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index 9bb0b29..239385c 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -106,10 +106,7 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container. startupPage = widget.NewSelect(pages, func(_ string) { s.config.Application.StartupPage = backend.SupportedStartupPages[startupPage.SelectedIndex()] }) - initialIdx := slices.Index(backend.SupportedStartupPages, s.config.Application.StartupPage) - if initialIdx < 0 { - initialIdx = 0 - } + initialIdx := max(slices.Index(backend.SupportedStartupPages, s.config.Application.StartupPage), 0) startupPage.SetSelectedIndex(initialIdx) if startupPage.Selected == "" { startupPage.SetSelectedIndex(0) diff --git a/ui/layouts/columnslayout.go b/ui/layouts/columnslayout.go index 9ae141f..90b1e7f 100644 --- a/ui/layouts/columnslayout.go +++ b/ui/layouts/columnslayout.go @@ -23,7 +23,7 @@ func NewColumnsLayout(widths []float32) *ColumnsLayout { func (c *ColumnsLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { var width float32 var height float32 - for i := 0; i < len(objects); i++ { + for i := range objects { if !objects[i].Visible() { continue } @@ -56,7 +56,7 @@ func (c *ColumnsLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { expandObjW := extraW / float32(expandObjCount) var x float32 - for i := 0; i < len(objects); i++ { + for i := range objects { if !objects[i].Visible() { continue } diff --git a/ui/theme/theme.go b/ui/theme/theme.go index feb3435..43e6095 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -341,10 +341,7 @@ func darkenColor(c color.Color, fraction float64) color.Color { } func brightenComponent(component uint32, fraction float64) uint32 { - brightened := component + uint32(float64(component)*fraction) - if brightened > 0xffff { - brightened = 0xffff - } + brightened := min(component+uint32(float64(component)*fraction), 0xffff) return brightened } diff --git a/ui/widgets/albumfilterbutton.go b/ui/widgets/albumfilterbutton.go index 43cb30c..7985f95 100644 --- a/ui/widgets/albumfilterbutton.go +++ b/ui/widgets/albumfilterbutton.go @@ -236,7 +236,7 @@ type GenreFilterSubsection struct { genreList []string onChanged func([]string) - selectedGenres map[string]interface{} + selectedGenres map[string]any selectedGenresMutex sync.RWMutex filterText *widget.Entry @@ -253,7 +253,7 @@ type GenreFilterSubsection struct { func NewGenreFilterSubsection(onChanged func([]string), initialSelectedGenres []string) *GenreFilterSubsection { g := &GenreFilterSubsection{ onChanged: onChanged, - selectedGenres: make(map[string]interface{}), + selectedGenres: make(map[string]any), } g.ExtendBaseWidget(g) diff --git a/ui/widgets/starrating.go b/ui/widgets/starrating.go index 8409538..c7a980c 100644 --- a/ui/widgets/starrating.go +++ b/ui/widgets/starrating.go @@ -45,7 +45,7 @@ func NewStarRating() *StarRating { func (s *StarRating) createContainer() { s.container = container.New(layout.NewCustomPaddedHBoxLayout(0)) var im *canvas.Image - for i := 0; i < 5; i++ { + for i := range 5 { if s.IsDisabled { im = canvas.NewImageFromResource(themedDisabledStarOutline) } else if s.Rating > i { @@ -126,7 +126,7 @@ func (s *StarRating) Refresh() { if !s.holdRating && s.mouseHoverRating > 0 { rating = s.mouseHoverRating } - for i := 0; i < 5; i++ { + for i := range 5 { im := s.container.Objects[i].(*canvas.Image) im.SetMinSize(fyne.NewSize(s.StarSize, s.StarSize)) if s.IsDisabled { diff --git a/ui/widgets/tracklistloader.go b/ui/widgets/tracklistloader.go index cc1a235..88619ef 100644 --- a/ui/widgets/tracklistloader.go +++ b/ui/widgets/tracklistloader.go @@ -58,7 +58,7 @@ func (t *TracklistLoader) loadMoreTracks(num int) { t.trackBuffer = make([]*mediaprovider.Track, 0, num) } t.trackBuffer = t.trackBuffer[:0] - for i := 0; i < num; i++ { + for range num { tr := t.iter.Next() if tr == nil { t.done = true From 08f335da44c8ddcf5e61faf7eac776c3ae1d9da7 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 17 Sep 2025 22:30:49 +0200 Subject: [PATCH 2/7] Run gofumpt on the project --- backend/config.go | 2 +- backend/imagecache.go | 5 +--- backend/ipc/client.go | 1 - .../jellyfin/jellyfinmediaprovider.go | 2 +- backend/mediaprovider/mediaprovider.go | 8 +++-- .../subsonic/subsonicmediaprovider.go | 6 ++-- backend/mpris.go | 4 +-- backend/player/jukebox/jukeboxplayer.go | 1 - backend/player/mpv/peaks.go | 1 + backend/player/mpv/player.go | 2 +- backend/savedplayqueue.go | 2 +- backend/waveformimage.go | 4 +-- backend/windows/smtc.go | 6 ++-- backend/windows/smtc_unsupported.go | 6 ++-- backend/windows/taskbar_buttons.go | 1 + res/wintaskbarthumbs/embed.go | 30 +++++++++---------- sharedutil/sharedutil_test.go | 1 - ui/browsing/artistpage.go | 2 +- ui/browsing/genrespage.go | 5 ++-- ui/browsing/nowplayingpage.go | 7 +++-- ui/browsing/playlistspage.go | 5 ++-- ui/browsing/radiospage.go | 8 ++--- ui/controller/controller.go | 3 -- ui/controller/playlist.go | 1 - ui/controller/routes.go | 1 + ui/controller/serverconnection.go | 1 - ui/dialogs/searchdialog.go | 1 - ui/dialogs/settingsdialog.go | 3 +- ui/shortcuts/shortcuts.go | 6 ++-- ui/toastoverlay.go | 1 - ui/util/svg.go | 6 ++-- ui/util/util.go | 2 +- ui/visualizations/peakmeter.go | 2 +- ui/widgets/auxcontrols.go | 2 +- ui/widgets/focuslist.go | 12 ++++---- ui/widgets/gridviewitem.go | 6 ++-- ui/widgets/loadingdots.go | 6 ++-- ui/widgets/multihyperlink.go | 10 +++---- ui/widgets/playqueuelist.go | 1 - ui/widgets/starrating.go | 2 +- ui/widgets/tracklistrow.go | 1 + 41 files changed, 92 insertions(+), 84 deletions(-) diff --git a/backend/config.go b/backend/config.go index 47e6fe3..acf53a6 100644 --- a/backend/config.go +++ b/backend/config.go @@ -317,7 +317,7 @@ func (c *Config) WriteConfigFile(filepath string) error { if err != nil { return err } - os.WriteFile(filepath, b, 0644) + os.WriteFile(filepath, b, 0o644) return nil } diff --git a/backend/imagecache.go b/backend/imagecache.go index 8de7a68..cd52212 100644 --- a/backend/imagecache.go +++ b/backend/imagecache.go @@ -39,9 +39,7 @@ type ImageCache struct { cache map[string]CacheItem } -var ( - ErrNotFound = errors.New("item not found") -) +var ErrNotFound = errors.New("item not found") func (i *ImageCache) Init(ctx context.Context, evictionInterval time.Duration) { i.cache = make(map[string]CacheItem) @@ -238,5 +236,4 @@ func (i *ImageCache) EvictExpired() { delete(i.cache, key) } } - } diff --git a/backend/ipc/client.go b/backend/ipc/client.go index 3b91ad6..ac23e3f 100644 --- a/backend/ipc/client.go +++ b/backend/ipc/client.go @@ -130,7 +130,6 @@ func (c *Client) Quit() error { func (c *Client) sendRequest(path string) (string, error) { resp, err := c.httpC.Get("http://supersonic/" + path) - if err != nil { return "", err } diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 7b5fe6f..0239a77 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -438,7 +438,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track { Duration: time.Duration(ch.RunTimeTicks/runTimeTicksPerMicrosecond) * time.Microsecond, TrackNumber: ch.IndexNumber, DiscNumber: ch.DiscNumber, - //Genre: ch.Genres, + // Genre: ch.Genres, ArtistIDs: artistIDs, ArtistNames: artistNames, Album: ch.Album, diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index 5fa2c48..704a8fa 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -32,9 +32,11 @@ type MediaIterator[M any] interface { Next() *M } -type ArtistIterator = MediaIterator[Artist] -type AlbumIterator = MediaIterator[Album] -type TrackIterator = MediaIterator[Track] +type ( + ArtistIterator = MediaIterator[Artist] + AlbumIterator = MediaIterator[Album] + TrackIterator = MediaIterator[Track] +) type MediaFilter[M, F any] interface { Options() F diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index ea04a31..fcf2c11 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -298,7 +298,8 @@ func (s *subsonicMediaProvider) ClientDecidesScrobble() bool { return true } func (s *subsonicMediaProvider) TrackBeganPlayback(trackID string) error { return s.client.Scrobble(trackID, map[string]string{ "time": strconv.FormatInt(time.Now().UnixMilli(), 10), - "submission": "false"}) + "submission": "false", + }) } func (s *subsonicMediaProvider) TrackEndedPlayback(trackID string, _ int, submission bool) error { @@ -307,7 +308,8 @@ func (s *subsonicMediaProvider) TrackEndedPlayback(trackID string, _ int, submis } return s.client.Scrobble(trackID, map[string]string{ "time": strconv.FormatInt(time.Now().UnixMilli(), 10), - "submission": "true"}) + "submission": "true", + }) } func (s *subsonicMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters, favorite bool) error { diff --git a/backend/mpris.go b/backend/mpris.go index f2921a4..72fe67d 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -24,9 +24,7 @@ var ( _ types.OrgMprisMediaPlayer2PlayerAdapterLoopStatus = (*MPRISHandler)(nil) ) -var ( - errNotSupported = errors.New("not supported") -) +var errNotSupported = errors.New("not supported") type MPRISHandler struct { // Function called if the player is requested to quit through MPRIS. diff --git a/backend/player/jukebox/jukeboxplayer.go b/backend/player/jukebox/jukeboxplayer.go index fd7be0a..2358b9c 100644 --- a/backend/player/jukebox/jukeboxplayer.go +++ b/backend/player/jukebox/jukeboxplayer.go @@ -109,7 +109,6 @@ func (j *JukeboxPlayer) SetNextTrack(track *mediaprovider.Track) error { } j.queueLength += 1 return nil - } func (j *JukeboxPlayer) SeekSeconds(secs float64) error { diff --git a/backend/player/mpv/peaks.go b/backend/player/mpv/peaks.go index afc8722..95471ce 100644 --- a/backend/player/mpv/peaks.go +++ b/backend/player/mpv/peaks.go @@ -3,6 +3,7 @@ package mpv // #include // int mpv_get_peaks(mpv_handle* handle, double* lPeak, double* rPeak, double* lRMS, double* rRMS); import "C" + import ( "github.com/supersonic-app/go-mpv" ) diff --git a/backend/player/mpv/player.go b/backend/player/mpv/player.go index 3c5965a..acf1dd7 100644 --- a/backend/player/mpv/player.go +++ b/backend/player/mpv/player.go @@ -492,7 +492,7 @@ func (p *Player) eventHandler(ctx context.Context) { default: e := p.mpv.WaitEvent(1 /*timeout seconds*/) if e.Event_Id != mpv.EVENT_NONE { - //log.Printf("mpv event: %+v\n", e) + // log.Printf("mpv event: %+v\n", e) } switch e.Event_Id { case mpv.EVENT_PLAYBACK_RESTART: diff --git a/backend/savedplayqueue.go b/backend/savedplayqueue.go index 4a25af1..b7ec5ed 100644 --- a/backend/savedplayqueue.go +++ b/backend/savedplayqueue.go @@ -51,7 +51,7 @@ func SavePlayQueue(serverID string, pm *PlaybackManager, filepath string, server TimePos: stats.TimePos, } b, _ := json.Marshal(saved) - err := os.WriteFile(filepath, b, 0644) + err := os.WriteFile(filepath, b, 0o644) if server != nil { // save to server diff --git a/backend/waveformimage.go b/backend/waveformimage.go index 117155b..89d1603 100644 --- a/backend/waveformimage.go +++ b/backend/waveformimage.go @@ -308,7 +308,7 @@ func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformDat currentSize := stat.Size() // how many bytes can we read without nearing EOF - readableBytes := currentSize - int64(samplesPerChunk)*int64(curChunk)*bytesPerSample - 8192 //buffer for safety + readableBytes := currentSize - int64(samplesPerChunk)*int64(curChunk)*bytesPerSample - 8192 // buffer for safety // Estimate how many samples we can read maxSamples := int(readableBytes / bytesPerSample) @@ -391,7 +391,7 @@ func computePeakAndRMS(chunk []float64) (peak float64, rms float64) { sumSquares += float64(v * v) } rms = math.Sqrt(sumSquares / float64(len(chunk))) - return + return peak, rms } func float64ToByte(val float64) byte { diff --git a/backend/windows/smtc.go b/backend/windows/smtc.go index 182af90..8637a05 100644 --- a/backend/windows/smtc.go +++ b/backend/windows/smtc.go @@ -17,8 +17,10 @@ import ( "golang.org/x/sys/windows" ) -type SMTCPlaybackState int -type SMTCButton int +type ( + SMTCPlaybackState int + SMTCButton int +) const ( // constants from smtc.h in github.com/supersonic-app/smtc-dll diff --git a/backend/windows/smtc_unsupported.go b/backend/windows/smtc_unsupported.go index e00f7d3..132c50b 100644 --- a/backend/windows/smtc_unsupported.go +++ b/backend/windows/smtc_unsupported.go @@ -4,8 +4,10 @@ package windows import "errors" -type SMTCPlaybackState int -type SMTCButton int +type ( + SMTCPlaybackState int + SMTCButton int +) const ( // constants from smtc.h in github.com/supersonic-app/smtc-dll diff --git a/backend/windows/taskbar_buttons.go b/backend/windows/taskbar_buttons.go index 782008e..09db0fb 100644 --- a/backend/windows/taskbar_buttons.go +++ b/backend/windows/taskbar_buttons.go @@ -10,6 +10,7 @@ package windows extern void goButtonClicked(int); */ import "C" + import ( "errors" "image" diff --git a/res/wintaskbarthumbs/embed.go b/res/wintaskbarthumbs/embed.go index fa48d2c..20bd6cd 100644 --- a/res/wintaskbarthumbs/embed.go +++ b/res/wintaskbarthumbs/embed.go @@ -1,15 +1,15 @@ -package wintaskbarthumbs - -import _ "embed" - -//go:embed media_pause.png -var MediaPausePNG []byte - -//go:embed media_play.png -var MediaPlayPNG []byte - -//go:embed media_seek_next.png -var MediaSeekNextPNG []byte - -//go:embed media_seek_previous.png -var MediaSeekPreviousPNG []byte +package wintaskbarthumbs + +import _ "embed" + +//go:embed media_pause.png +var MediaPausePNG []byte + +//go:embed media_play.png +var MediaPlayPNG []byte + +//go:embed media_seek_next.png +var MediaSeekNextPNG []byte + +//go:embed media_seek_previous.png +var MediaSeekPreviousPNG []byte diff --git a/sharedutil/sharedutil_test.go b/sharedutil/sharedutil_test.go index f4d6e18..ab84da4 100644 --- a/sharedutil/sharedutil_test.go +++ b/sharedutil/sharedutil_test.go @@ -8,7 +8,6 @@ import ( ) func Test_ReorderItems(t *testing.T) { - tracks := []*mediaprovider.Track{ {ID: "a"}, // 0 {ID: "b"}, // 1 diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index f389658..8059fd9 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -486,7 +486,7 @@ type ArtistPageHeader struct { menuBtn *widget.Button container *fyne.Container fullSizeCoverFetching bool - //shareMenuItem *fyne.MenuItem + // shareMenuItem *fyne.MenuItem } func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader { diff --git a/ui/browsing/genrespage.go b/ui/browsing/genrespage.go index ceeea50..85031f3 100644 --- a/ui/browsing/genrespage.go +++ b/ui/browsing/genrespage.go @@ -204,7 +204,8 @@ func NewGenreList(sorting widgets.ListHeaderSort) *GenreList { a.hdr = widgets.NewListHeader([]widgets.ListColumn{ {Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, {Text: albumCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}, - {Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}, + {Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}, + }, a.columnsLayout) a.hdr.SetSorting(sorting) a.hdr.OnColumnSortChanged = a.onSorted @@ -261,7 +262,7 @@ func (g *GenreList) doSortGenres() { return } switch g.sorting.ColNumber { - case 0: //Name + case 0: // Name g.stringSort(func(g *mediaprovider.Genre) string { return g.Name }) case 1: // Album Count g.intSort(func(g *mediaprovider.Genre) int { return g.AlbumCount }) diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 37ae136..7059d34 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -360,8 +360,10 @@ func (a *NowPlayingPage) updateLyrics() { // set the widget to an empty (not nil) lyric during fetch // to keep it from showing "Lyrics not available" a.lyricsViewer.DisableTapToSeek() - a.lyricsViewer.SetLyrics(&mediaprovider.Lyrics{Synced: true, - Lines: []mediaprovider.LyricLine{{Text: ""}}}) + a.lyricsViewer.SetLyrics(&mediaprovider.Lyrics{ + Synced: true, + Lines: []mediaprovider.LyricLine{{Text: ""}}, + }) tr, _ := a.nowPlaying.(*mediaprovider.Track) go a.fetchLyrics(ctx, tr) } @@ -482,7 +484,6 @@ func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64, seeked bool) { func (a *NowPlayingPage) currentTracklistOrNil() *widgets.PlayQueueList { if a.tabs != nil { - switch a.tabs.SelectedIndex() { case 0: /*queue*/ return a.queueList diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index 5bea3b5..81e16c5 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -389,7 +389,8 @@ func (p *PlaylistList) buildHeaderAndLayout() { {Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, {Text: lang.L("_Description"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, {Text: lang.L("Owner"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, - {Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}, p.columnsLayout) + {Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}, + }, p.columnsLayout) p.header.SetSorting(p.sorting) p.header.OnColumnSortChanged = p.onSorted } @@ -422,7 +423,7 @@ func (p *PlaylistList) doSortPlaylists() { return } switch p.sorting.ColNumber { - case 0: //Name + case 0: // Name p.stringSort(func(p *mediaprovider.Playlist) string { return p.Name }) case 1: // Description p.stringSort(func(p *mediaprovider.Playlist) string { return p.Description }) diff --git a/ui/browsing/radiospage.go b/ui/browsing/radiospage.go index a43b447..543a81c 100644 --- a/ui/browsing/radiospage.go +++ b/ui/browsing/radiospage.go @@ -253,7 +253,8 @@ func NewRadioList(nowPlayingIDPtr *string) *RadioList { a.ExtendBaseWidget(a) a.hdr = widgets.NewListHeader([]widgets.ListColumn{ {Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, - {Text: lang.L("Home Page"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}}, + {Text: lang.L("Home Page"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, + }, a.columnsLayout) a.hdr.DisableSorting = true a.list = widgets.NewFocusList( @@ -299,9 +300,8 @@ func NewRadioList(nowPlayingIDPtr *string) *RadioList { row.IsPlaying = isPlaying row.nameLabel.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying if isPlaying { - row.Content.(*fyne.Container).Objects[0] = - container.NewBorder(nil, nil, a.playingIcon, nil, - container.New(layout.NewCustomPaddedLayout(0, 0, -5, 0), row.nameLabel)) + row.Content.(*fyne.Container).Objects[0] = container.NewBorder(nil, nil, a.playingIcon, nil, + container.New(layout.NewCustomPaddedLayout(0, 0, -5, 0), row.nameLabel)) } else { row.Content.(*fyne.Container).Objects[0] = row.nameLabel } diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 927cd6f..61b70fd 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -309,7 +309,6 @@ func (c *Controller) ShowAboutDialog() { } func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map[string]string) { - devs, err := c.App.LocalPlayer.ListAudioDevices() if err != nil { log.Printf("error listing audio devices: %v", err) @@ -358,7 +357,6 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map c.ClosePopUpOnEscape(pop) c.haveModal = true pop.Show() - } func (c *Controller) doModalClosed() { @@ -467,7 +465,6 @@ func (c *Controller) ShowDownloadDialog(tracks []*mediaprovider.Track, downloadN } else { go c.downloadTracks(tracks, file.URI().Path(), downloadName) } - }, c.MainWindow) dg.SetFileName(fileName) diff --git a/ui/controller/playlist.go b/ui/controller/playlist.go index eec2698..1dd2e6e 100644 --- a/ui/controller/playlist.go +++ b/ui/controller/playlist.go @@ -88,7 +88,6 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { }() } } - }) m.ClosePopUpOnEscape(pop) m.haveModal = true diff --git a/ui/controller/routes.go b/ui/controller/routes.go index ad297c5..5f4f87e 100644 --- a/ui/controller/routes.go +++ b/ui/controller/routes.go @@ -81,6 +81,7 @@ func GenresRoute() Route { func PlaylistRoute(id string) Route { return Route{Page: Playlist, Arg: id} } + func PlaylistsRoute() Route { return Route{Page: Playlists} } diff --git a/ui/controller/serverconnection.go b/ui/controller/serverconnection.go index d917d5e..3699efc 100644 --- a/ui/controller/serverconnection.go +++ b/ui/controller/serverconnection.go @@ -200,7 +200,6 @@ func (m *Controller) PromptForLoginAndConnect() { pop.Show() } }, m.MainWindow) - } m.haveModal = true pop.Show() diff --git a/ui/dialogs/searchdialog.go b/ui/dialogs/searchdialog.go index 411c974..faf2d4b 100644 --- a/ui/dialogs/searchdialog.go +++ b/ui/dialogs/searchdialog.go @@ -374,5 +374,4 @@ func (q *searchEntry) TypedKey(e *fyne.KeyEvent) { default: q.SearchEntry.TypedKey(e) } - } diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index 239385c..d085ed5 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -488,7 +488,8 @@ func (s *SettingsDialog) createAppearanceTab(window fyne.Window) *container.TabI themeModeSelect := widget.NewSelect([]string{ string(myTheme.AppearanceDark), string(myTheme.AppearanceLight), - string(myTheme.AppearanceAuto)}, nil) + string(myTheme.AppearanceAuto), + }, nil) themeModeSelect.OnChanged = func(_ string) { s.config.Theme.Appearance = themeModeSelect.Options[themeModeSelect.SelectedIndex()] if s.OnThemeSettingChanged != nil { diff --git a/ui/shortcuts/shortcuts.go b/ui/shortcuts/shortcuts.go index 96a1dcb..541ad65 100644 --- a/ui/shortcuts/shortcuts.go +++ b/ui/shortcuts/shortcuts.go @@ -20,6 +20,8 @@ var ( ShortcutNavSeven = desktop.CustomShortcut{KeyName: fyne.Key7, Modifier: fyne.KeyModifierShortcutDefault} ShortcutNavEight = desktop.CustomShortcut{KeyName: fyne.Key8, Modifier: fyne.KeyModifierShortcutDefault} - NavShortcuts = []desktop.CustomShortcut{ShortcutNavOne, ShortcutNavTwo, ShortcutNavThree, - ShortcutNavFour, ShortcutNavFive, ShortcutNavSix, ShortcutNavSeven, ShortcutNavEight} + NavShortcuts = []desktop.CustomShortcut{ + ShortcutNavOne, ShortcutNavTwo, ShortcutNavThree, + ShortcutNavFour, ShortcutNavFive, ShortcutNavSix, ShortcutNavSeven, ShortcutNavEight, + } ) diff --git a/ui/toastoverlay.go b/ui/toastoverlay.go index 4faea5a..abd7361 100644 --- a/ui/toastoverlay.go +++ b/ui/toastoverlay.go @@ -89,7 +89,6 @@ func (t *ToastOverlay) makeToastAnimFunc(endPos fyne.Position, dismissal bool) f t.cancelPreviousToast() } } - } } diff --git a/ui/util/svg.go b/ui/util/svg.go index 90edfbe..ee57052 100644 --- a/ui/util/svg.go +++ b/ui/util/svg.go @@ -225,7 +225,7 @@ func colorToHexAndOpacity(color color.Color) (hexStr, aStr string) { r, g, b, a := toNRGBA(color) cBytes := []byte{byte(r), byte(g), byte(b)} hexStr, aStr = "#"+hex.EncodeToString(cBytes), strconv.FormatFloat(float64(a)/0xff, 'f', 6, 64) - return + return hexStr, aStr } // toNRGBA converts a color to RGBA values which are not premultiplied, unlike color.RGBA(). @@ -300,7 +300,7 @@ func toNRGBA(c color.Color) (r, g, b, a int) { default: // RGBA, RGBA64, and unknown implementations of Color r, g, b, a = unmultiplyAlpha(c) } - return + return r, g, b, a } // unmultiplyAlpha returns a color's RGBA components as 8-bit integers by calling c.RGBA() and then removing the alpha premultiplication. @@ -317,5 +317,5 @@ func unmultiplyAlpha(c color.Color) (r, g, b, a int) { g = int(green >> 8) b = int(blue >> 8) a = int(alpha >> 8) - return + return r, g, b, a } diff --git a/ui/util/util.go b/ui/util/util.go index e557c0a..5bab70b 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -55,7 +55,7 @@ func dateFormatForLocale(locale string) DateFormat { } func shortMonthName(month int) string { - var months = [12]string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"} + months := [12]string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"} if month >= 1 && month <= 12 { return lang.L(months[month-1]) } diff --git a/ui/visualizations/peakmeter.go b/ui/visualizations/peakmeter.go index 7e4df3c..c740736 100644 --- a/ui/visualizations/peakmeter.go +++ b/ui/visualizations/peakmeter.go @@ -210,7 +210,7 @@ func (l *peakMeterRenderer) Refresh() { func (l *peakMeterRenderer) Objects() []fyne.CanvasObject { if l.objects == nil { - l.objects = make([]fyne.CanvasObject, 0, 2*len(l.rulerLines) + 8) + l.objects = make([]fyne.CanvasObject, 0, 2*len(l.rulerLines)+8) for i := range l.rulerLines { l.objects = append(l.objects, &l.rulerLines[i], &l.rulerLabels[i]) } diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 324bce1..558ea72 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -49,7 +49,7 @@ func NewAuxControls(initialVolume int, initialLoopMode backend.LoopMode, initial a.cast.SetToolTip(lang.L("Cast to device")) a.autoplay.Highlighted = initialAutoplay - //a.autoplay.IconSize = IconButtonSizeSmaller + // a.autoplay.IconSize = IconButtonSizeSmaller a.autoplay.SetToolTip(lang.L("Autoplay")) a.autoplay.OnTapped = func() { a.SetAutoplay(!a.autoplay.Highlighted) diff --git a/ui/widgets/focuslist.go b/ui/widgets/focuslist.go index e4f1330..f0507f8 100644 --- a/ui/widgets/focuslist.go +++ b/ui/widgets/focuslist.go @@ -71,10 +71,12 @@ func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) { } } -var _ fyne.Tappable = (*FocusListRowBase)(nil) -var _ fyne.Widget = (*FocusListRowBase)(nil) -var _ fyne.Focusable = (*FocusListRowBase)(nil) -var _ desktop.Hoverable = (*FocusListRowBase)(nil) +var ( + _ fyne.Tappable = (*FocusListRowBase)(nil) + _ fyne.Widget = (*FocusListRowBase)(nil) + _ fyne.Focusable = (*FocusListRowBase)(nil) + _ desktop.Hoverable = (*FocusListRowBase)(nil) +) // Base type used for all list rows in widgets such as Tracklist, etc. type FocusListRowBase struct { @@ -88,7 +90,7 @@ type FocusListRowBase struct { OnTapped func() OnDoubleTapped func() - OnFocusNeighbor func(up bool) //TODO: func(up, selecting bool) + OnFocusNeighbor func(up bool) // TODO: func(up, selecting bool) tappedAt int64 // unixMillis focusedRect canvas.Rectangle diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index f8911d9..5f82b8d 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -21,8 +21,10 @@ import ( "github.com/dweymouth/supersonic/ui/util" ) -var _ fyne.Widget = (*GridViewItem)(nil) -var _ fyne.Focusable = (*GridViewItem)(nil) +var ( + _ fyne.Widget = (*GridViewItem)(nil) + _ fyne.Focusable = (*GridViewItem)(nil) +) var _ fyne.Widget = (*coverImage)(nil) diff --git a/ui/widgets/loadingdots.go b/ui/widgets/loadingdots.go index 47d19c6..a84831d 100644 --- a/ui/widgets/loadingdots.go +++ b/ui/widgets/loadingdots.go @@ -28,8 +28,10 @@ type LoadingDots struct { func NewLoadingDots() *LoadingDots { l := &LoadingDots{} for i := range l.dots { - l.dots[i] = minSizeCircle{Circle: canvas.Circle{ - FillColor: theme.DisabledColor()}, + l.dots[i] = minSizeCircle{ + Circle: canvas.Circle{ + FillColor: theme.DisabledColor(), + }, } } l.ExtendBaseWidget(l) diff --git a/ui/widgets/multihyperlink.go b/ui/widgets/multihyperlink.go index 0e9ff2a..0737d43 100644 --- a/ui/widgets/multihyperlink.go +++ b/ui/widgets/multihyperlink.go @@ -31,7 +31,7 @@ type MultiHyperlink struct { // TODO: Once https://github.com/fyne-io/fyne/issues/4336 is resolved, // we can switch to the much cleaner RichText implementation - //provider *widget.RichText + // provider *widget.RichText objects []fyne.CanvasObject suffixLabel *ttwidget.RichText @@ -45,11 +45,11 @@ type MultiHyperlinkSegment struct { func NewMultiHyperlink() *MultiHyperlink { c := &MultiHyperlink{ - //provider: widget.NewRichText(), + // provider: widget.NewRichText(), content: container.NewWithoutLayout(), } c.ExtendBaseWidget(c) - //c.provider.Truncation = fyne.TextTruncateEllipsis + // c.provider.Truncation = fyne.TextTruncateEllipsis return c } @@ -302,12 +302,12 @@ func (c *MultiHyperlink) Resize(size fyne.Size) { } func (c *MultiHyperlink) Refresh() { - //c.syncSegments() + // c.syncSegments() c.layoutObjects() c.BaseWidget.Refresh() } func (c *MultiHyperlink) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(c.content) - //return widget.NewSimpleRenderer(c.provider) + // return widget.NewSimpleRenderer(c.provider) } diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index ba536ed..06947ce 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -264,7 +264,6 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { p.ensureRadiosMenu() p.radiosMenu.ShowAtPosition(e.AbsolutePosition) } - } func (p *PlayQueueList) ensureTracksMenu() { diff --git a/ui/widgets/starrating.go b/ui/widgets/starrating.go index c7a980c..59239e5 100644 --- a/ui/widgets/starrating.go +++ b/ui/widgets/starrating.go @@ -103,7 +103,7 @@ var _ fyne.Tappable = (*StarRating)(nil) func (s *StarRating) Tapped(*fyne.PointEvent) { if s.mouseHoverRating <= 0 { - return //shouldn't happen + return // shouldn't happen } if s.Rating == s.mouseHoverRating { s.Rating = 0 diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index 411418e..397a197 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -352,6 +352,7 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) { t.OnTappedSecondary = f } + func (t *tracklistRowBase) TrackID() string { return t.trackID } From ebc5b7e039050223941f9d8e6249b7b76f1be01b Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 17 Sep 2025 22:39:36 +0200 Subject: [PATCH 3/7] Fix all staticcheck errors that are not deprecations --- backend/cmdlineoptions.go | 4 +--- backend/mediaprovider/subsonic/artistiterator.go | 11 +++-------- backend/windows/smtc_unsupported.go | 14 +++++++------- ui/controller/controller.go | 3 ++- ui/widgets/gridviewitem.go | 5 ++--- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/backend/cmdlineoptions.go b/backend/cmdlineoptions.go index 22e792c..1f968a5 100644 --- a/backend/cmdlineoptions.go +++ b/backend/cmdlineoptions.go @@ -58,9 +58,7 @@ func init() { return err }) flag.Func("volume-adjust-pct", "adjusts volume up or down by the given percentage (positive or negative)", func(s string) error { - if strings.HasSuffix(s, "%") { - s = s[:len(s)-1] - } + s = strings.TrimSuffix(s, "%") v, err := strconv.ParseFloat(s, 64) VolumePctCLIArg = v return err diff --git a/backend/mediaprovider/subsonic/artistiterator.go b/backend/mediaprovider/subsonic/artistiterator.go index 35e2587..d6b21be 100644 --- a/backend/mediaprovider/subsonic/artistiterator.go +++ b/backend/mediaprovider/subsonic/artistiterator.go @@ -22,11 +22,8 @@ func (s *subsonicMediaProvider) ArtistSortOrders() []string { } } -func filterArtistMatches(f mediaprovider.ArtistFilter, artist *subsonic.ArtistID3) bool { - if artist == nil { - return false - } - return true +func filterArtistMatches(_ mediaprovider.ArtistFilter, artist *subsonic.ArtistID3) bool { + return artist != nil } func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator { @@ -176,9 +173,7 @@ func (s *subsonicMediaProvider) artistFetchFnFromStandardSort(sortFn func([]*sub } var artists []*subsonic.ArtistID3 for _, idx := range idxs.Index { - for _, ar := range idx.Artist { - artists = append(artists, ar) - } + artists = append(artists, idx.Artist...) } artists = sortFn(artists) return artists, nil diff --git a/backend/windows/smtc_unsupported.go b/backend/windows/smtc_unsupported.go index 132c50b..5a64846 100644 --- a/backend/windows/smtc_unsupported.go +++ b/backend/windows/smtc_unsupported.go @@ -24,18 +24,18 @@ const ( type SMTC struct{} -var smtcUnsupportedErr = errors.New("SMTC is not supported on this platformo") +var errSMTCUnsupported = errors.New("no support for SMTC on this platform") func InitSMTCForWindow(hwnd uintptr) (*SMTC, error) { - return nil, smtcUnsupportedErr + return nil, errSMTCUnsupported } func (s *SMTC) SetEnabled(enabled bool) error { - return smtcUnsupportedErr + return errSMTCUnsupported } func (s *SMTC) SetThumbnail(filepath string) error { - return smtcUnsupportedErr + return errSMTCUnsupported } func (s *SMTC) OnButtonPressed(func(SMTCButton)) {} @@ -45,13 +45,13 @@ func (s *SMTC) OnSeek(f func(millis int)) {} func (s *SMTC) Shutdown() {} func (s *SMTC) UpdatePlaybackState(state SMTCPlaybackState) error { - return smtcUnsupportedErr + return errSMTCUnsupported } func (s *SMTC) UpdateMetadata(title, artist string) error { - return smtcUnsupportedErr + return errSMTCUnsupported } func (s *SMTC) UpdatePosition(positionMillis, durationMillis int) error { - return smtcUnsupportedErr + return errSMTCUnsupported } diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 61b70fd..83b56e5 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -2,6 +2,7 @@ package controller import ( "archive/zip" + "errors" "fmt" "image" "image/color" @@ -546,7 +547,7 @@ func (c *Controller) sendNotification(title, content string) { func (c *Controller) showError(content string) { // TODO: display an in-app toast message instead of a dialog. - dialog.ShowError(fmt.Errorf(content), c.MainWindow) + dialog.ShowError(errors.New(content), c.MainWindow) } func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover image.Image) { diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index 5f82b8d..291b72a 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -378,11 +378,10 @@ func setPlayBtnTranslucency(f float32) { // get theme Primary color as color.NRGBA var primary color.NRGBA - switch pr := theme.Color(theme.ColorNamePrimary); pr.(type) { + switch pr := theme.Color(theme.ColorNamePrimary).(type) { case color.NRGBA: - primary = pr.(color.NRGBA) + primary = pr case color.RGBA: - pr := pr.(color.RGBA) primary = color.NRGBA{R: pr.R, G: pr.G, B: pr.B, A: pr.A} } From 2fa02343215ebf1016b580cd7921374bac448c24 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 22 Sep 2025 18:44:48 -0700 Subject: [PATCH 4/7] omit album and artist from LrcLib search if unknown/emtpy --- backend/lrclib.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/lrclib.go b/backend/lrclib.go index feb5234..24fb6c1 100644 --- a/backend/lrclib.go +++ b/backend/lrclib.go @@ -80,10 +80,26 @@ func (l *LrcLibFetcher) fetchFromServer(name, artist, album string, durationSecs req.Header.Add("Accept", "application/json") req.Header.Add("User-Agent", "Supersonic") + // Navidrome and Gonic substitute "[Unknown Album]" and "Unknown Album", respectively, + // for an empty album name. This will break LrcLib matching. + // TODO: if OpenSubsonic later clarifies that servers should not do this, remove this workaround. + // N.B.: This workaround will break if servers decide to internationalize the default album name + if strings.Contains(album, "Unknown Album") { + album = "" + } + if strings.Contains(artist, "Unknown Artist") { + artist = "" + } + q := req.URL.Query() + addIf := func(key, value string) { + if value != "" { + q.Add(key, value) + } + } q.Add("track_name", name) - q.Add("artist_name", artist) - q.Add("album_name", album) + addIf("artist_name", artist) + addIf("album_name", album) q.Add("duration", strconv.Itoa(durationSecs)) req.URL.RawQuery = q.Encode() From d5c47b65fa3c7f6b916d8930a5b5ed260e637fc5 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 24 Sep 2025 11:02:12 -0700 Subject: [PATCH 5/7] bump windows mpv version --- .github/workflows/build-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 401ae8c..76cff5a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -45,8 +45,8 @@ jobs: - name: Download mpv dll run: > - wget https://github.com/shinchiro/mpv-winbuild-cmake/releases/download/20250810/mpv-dev-x86_64-20250810-git-01b7edc.7z && - 7z x mpv-dev-x86_64-20250810-git-01b7edc.7z + wget https://github.com/shinchiro/mpv-winbuild-cmake/releases/download/20250921/mpv-dev-x86_64-20250921-git-f147b13.7z && + 7z x mpv-dev-x86_64-20250921-git-f147b13.7z - name: Download smtc dll run: > From 4c1a95e851d94744e5c73c778bc26177f0988d31 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 24 Sep 2025 18:59:55 -0700 Subject: [PATCH 6/7] add settings for skipping one-star or tracks with keyword (#724) --- backend/config.go | 8 +++--- backend/playbackmanager.go | 50 +++++++++++++++++++++++------------- go.mod | 1 + go.sum | 2 ++ res/translations/en.json | 3 +++ ui/dialogs/settingsdialog.go | 9 ++++++- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/backend/config.go b/backend/config.go index acf53a6..29566bb 100644 --- a/backend/config.go +++ b/backend/config.go @@ -112,9 +112,11 @@ type NowPlayingPageConfig struct { } type PlaybackConfig struct { - Autoplay bool - RepeatMode string - UseWaveformSeekbar bool + Autoplay bool + RepeatMode string + SkipOneStarWhenShuffling bool + SkipKeywordWhenShuffling string + UseWaveformSeekbar bool } type LocalPlaybackConfig struct { diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 38e54f3..7da5eea 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/charlievieth/strcase" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/player" "github.com/dweymouth/supersonic/backend/player/dlna" @@ -27,7 +28,8 @@ type PlaybackManager struct { wfmGen *WaveformImageGenerator cache *AudioCache cmdQueue *playbackCommandQueue - cfg *AppConfig + appCfg *AppConfig + cfg *PlaybackConfig localPlayer player.BasePlayer remotePlayersLock sync.Mutex @@ -36,8 +38,6 @@ type PlaybackManager struct { onWaveformImgUpdate []func(*WaveformImage) - autoplay bool - lastPlayTime float64 lastPlayingID string wfmUpdateImageCancel context.CancelFunc @@ -66,8 +66,8 @@ func NewPlaybackManager( pm := &PlaybackManager{ engine: e, cmdQueue: q, - cfg: appCfg, - autoplay: playbackCfg.Autoplay, + appCfg: appCfg, + cfg: playbackCfg, localPlayer: p, cache: c, } @@ -117,7 +117,7 @@ func (p *PlaybackManager) addOnTrackChangeHook() { p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) { // Autoplay if enabled and we are on the last track - if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 { + if p.cfg.Autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 { p.enqueueAutoplayTracks() } p.handleWaveformImageSongChange(item) @@ -470,13 +470,23 @@ func (p *PlaybackManager) PlayTrackAt(idx int) { func (p *PlaybackManager) PlayRandomSongs(genreName string) error { return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) { - return p.engine.sm.Server.GetRandomTracks(genreName, p.cfg.EnqueueBatchSize) + tr, err := p.engine.sm.Server.GetRandomTracks(genreName, p.appCfg.EnqueueBatchSize) + if err != nil { + return nil, err + } + return sharedutil.FilterSlice(tr, func(t *mediaprovider.Track) bool { + skipKwd := p.cfg.SkipKeywordWhenShuffling + include := + (skipKwd == "" || !strcase.Contains(t.Title, skipKwd)) && + (!p.cfg.SkipOneStarWhenShuffling || t.Rating != 1) + return include + }), nil }) } func (p *PlaybackManager) PlaySimilarSongs(id string) error { return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) { - return p.engine.sm.Server.GetSimilarTracks(id, p.cfg.EnqueueBatchSize) + return p.engine.sm.Server.GetSimilarTracks(id, p.appCfg.EnqueueBatchSize) }) } @@ -592,7 +602,7 @@ func (p *PlaybackManager) GetLoopMode() LoopMode { } func (p *PlaybackManager) IsAutoplay() bool { - return p.autoplay + return p.cfg.Autoplay } func (p *PlaybackManager) PlaybackStatus() PlaybackStatus { @@ -604,7 +614,7 @@ func (p *PlaybackManager) SetVolume(vol int) { } func (p *PlaybackManager) SetAutoplay(autoplay bool) { - p.autoplay = autoplay + p.cfg.Autoplay = autoplay if autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 { p.enqueueAutoplayTracks() } @@ -705,11 +715,15 @@ func (p *PlaybackManager) enqueueAutoplayTracks() { // tracks we will enqueue var tracks []*mediaprovider.Track - filterRecentlyPlayed := func(tracks []*mediaprovider.Track) []*mediaprovider.Track { + filterAutoplayTracks := func(tracks []*mediaprovider.Track) []*mediaprovider.Track { return sharedutil.FilterSlice(tracks, func(t *mediaprovider.Track) bool { - return !slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool { + shouldSkip := + (p.cfg.SkipOneStarWhenShuffling && t.Rating == 1) || + (p.cfg.SkipKeywordWhenShuffling != "" && strcase.Contains(t.Title, p.cfg.SkipKeywordWhenShuffling)) + recentlyPlayed := slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool { return i.Metadata().Type == mediaprovider.MediaItemTypeTrack && i.Metadata().ID == t.ID }) + return !shouldSkip && !recentlyPlayed }) } @@ -722,11 +736,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() { // similar tracks by artist if len(tr.ArtistIDs) > 0 { - similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.cfg.EnqueueBatchSize) + similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.appCfg.EnqueueBatchSize) if err != nil { log.Printf("autoplay error: failed to get similar tracks: %v", err) } - tracks = filterRecentlyPlayed(similar) + tracks = filterAutoplayTracks(similar) } // fallback to random tracks from genre @@ -735,11 +749,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() { if g == "" { continue } - byGenre, err := s.GetRandomTracks(g, p.cfg.EnqueueBatchSize) + byGenre, err := s.GetRandomTracks(g, p.appCfg.EnqueueBatchSize) if err != nil { log.Printf("autoplay error: failed to get tracks by genre: %v", err) } - tracks = filterRecentlyPlayed(byGenre) + tracks = filterAutoplayTracks(byGenre) if len(tracks) > 0 { break } @@ -750,11 +764,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() { // random tracks works regardless of the type of the last playing media if len(tracks) == 0 { // fallback to random tracks - random, err := s.GetRandomTracks("", p.cfg.EnqueueBatchSize) + random, err := s.GetRandomTracks("", p.appCfg.EnqueueBatchSize) if err != nil { log.Printf("autoplay error: failed to get random tracks: %v", err) } - tracks = filterRecentlyPlayed(random) + tracks = filterAutoplayTracks(random) } if len(tracks) > 0 { diff --git a/go.mod b/go.mod index f0d0833..554e29c 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect fyne.io/systray v1.11.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect + github.com/charlievieth/strcase v0.0.5 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fogleman/gg v1.3.0 // indirect diff --git a/go.sum b/go.sum index b85fb41..b8d81f4 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/dominantcolor v1.0.3 h1:Pt0vfRZ8enkZh1n22RvoboA53SMM/v2aEwNQTZKSqww= github.com/cenkalti/dominantcolor v1.0.3/go.mod h1:mGpFMbWUnyXaGN48Zbf9bU9HJP1eCCD7dnsscb4lyR4= +github.com/charlievieth/strcase v0.0.5 h1:gV4iXVyD6eI5KdfOV+/vIVCKXZwtCWOmDMcu7Uy00Rs= +github.com/charlievieth/strcase v0.0.5/go.mod h1:FIOYY1aDBMSIOFqmVomHBpoK+bteGlESRsgsdWjrhx8= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/res/translations/en.json b/res/translations/en.json index 73fd979..02b5408 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -211,8 +211,10 @@ "Singles": "Singles", "Size": "Size", "Skip duplicate tracks": "Skip duplicate tracks", + "Skip one-star tracks": "Skip one-star tracks", "Skip SSL certificate verification": "Skip SSL certificate verification", "Skip this version": "Skip this version", + "Skip tracks with keyword": "Skip tracks with keyword", "Smaller": "Smaller", "Sort": "Sort", "Soundtrack": "Soundtrack", @@ -251,6 +253,7 @@ "version": "version", "Visualizations": "Visualizations", "Volume": "Volume", + "When enqueuing random": "When enqueuing random", "wrong URL": "wrong URL", "wrong username/password": "wrong username/password", "Year": "Year", diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index d085ed5..43a2cf5 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -427,12 +427,19 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo disableTranscode, container.NewHBox(transcode, transcodeCodec, transcodeBitRate), s.newSectionSeparator(), - widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}), + widget.NewLabelWithStyle("ReplayGain", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), container.New(layout.NewFormLayout(), widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect), widget.NewLabel(lang.L("ReplayGain preamp")), container.NewHBox(preampGain, widget.NewLabel("dB")), widget.NewLabel(lang.L("Prevent clipping")), preventClipping, ), + s.newSectionSeparator(), + widget.NewLabelWithStyle(lang.L("When enqueuing random"), fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewCheckWithData(lang.L("Skip one-star tracks"), binding.BindBool(&s.config.Playback.SkipOneStarWhenShuffling)), + container.NewBorder(nil, nil, + widget.NewLabel(lang.L("Skip tracks with keyword")), nil, + widget.NewEntryWithData(binding.BindString(&s.config.Playback.SkipKeywordWhenShuffling)), + ), )) } From 06b57d27d7577887931aaabb8a2b3d50e3382ba0 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 25 Sep 2025 09:37:14 -0700 Subject: [PATCH 7/7] Fix #716: add "Last played" tracklist columnn --- .../jellyfin/jellyfinmediaprovider.go | 3 + res/translations/en.json | 22 +++++++ res/translations/es.json | 22 +++++++ ui/util/util.go | 33 ++++++++++ ui/widgets/tracklist.go | 2 + ui/widgets/tracklistrow.go | 64 +++++++++++-------- 6 files changed, 120 insertions(+), 26 deletions(-) diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 0239a77..0f2d209 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -430,6 +430,8 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track { coverArtID = ch.Id } + lastPlayed, _ := time.Parse(time.RFC3339Nano, ch.UserData.LastPlayedDate) + t := &mediaprovider.Track{ ID: ch.Id, CoverArtID: coverArtID, @@ -447,6 +449,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track { Rating: ch.UserData.Rating, Favorite: ch.UserData.IsFavorite, PlayCount: ch.UserData.PlayCount, + LastPlayed: lastPlayed, } if len(ch.MediaSources) > 0 { t.FilePath = ch.MediaSources[0].Path diff --git a/res/translations/en.json b/res/translations/en.json index 02b5408..973db0b 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -131,6 +131,7 @@ "My Server": "My Server", "Name": "Name", "Name (A-Z)": "Name (A-Z)", + "never": "never", "Next": "Next", "Nickname": "Nickname", "Normal": "Normal", @@ -289,5 +290,26 @@ "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" + }, + "x_hours_ago": { + "one": "an hour ago", + "other": "{{.hours}} hours ago" + }, + "x_days_ago": { + "one": "a day ago", + "other": "{{.days}} days ago" + }, + "x_months_ago": { + "one": "a month ago", + "other": "{{.months}} months ago" + }, + "x_years_ago": { + "one": "a year ago", + "other": "{{.years}} years ago" } } diff --git a/res/translations/es.json b/res/translations/es.json index 12b5883..0bc29ee 100644 --- a/res/translations/es.json +++ b/res/translations/es.json @@ -113,6 +113,7 @@ "My Server": "Mi servidor", "Name": "Nombre", "Name (A-Z)": "Nombre (A-Z)", + "never": "nunca", "Next": "Siguiente", "Nickname": "Apodo", "Now Playing": "Reproduciendo", @@ -252,5 +253,26 @@ "playlist.addedtracks": { "one": "Se ha añadido una pista a la lista de reproducción", "other": "Se han añadido {{.trackCount}} pistas a la lista de reproducción" + }, + + "x_minutes_ago": { + "one": "hace un minuto", + "other": "hace {{.minutes}} minutos" + }, + "x_hours_ago": { + "one": "hace una hora", + "other": "hace {{.hours}} horas" + }, + "x_days_ago": { + "one": "hace un día", + "other": "hace {{.days}} días" + }, + "x_months_ago": { + "one": "hace un mes", + "other": "hace {{.months}} meses" + }, + "x_years_ago": { + "one": "hace un año", + "other": "hace {{.years}} años" } } diff --git a/ui/util/util.go b/ui/util/util.go index 5bab70b..000e8a6 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -100,6 +100,39 @@ func FormatItemDate(date mediaprovider.ItemDate) string { return sb.String() } +func LastPlayedDisplayString(t time.Time) string { + if t.IsZero() { + return lang.L("never") + } + switch d := time.Since(t); { + case d.Hours() < 1: + mins := int(d.Minutes()) + return lang.LocalizePluralKey("x_minutes_ago", + fmt.Sprintf("%d minutes ago", mins), mins, + map[string]string{"minutes": strconv.Itoa(mins)}) + case d.Hours() < 24: + hrs := int(d.Hours()) + return lang.LocalizePluralKey("x_hours_ago", + fmt.Sprintf("%d hours ago", hrs), hrs, + map[string]string{"hours": strconv.Itoa(hrs)}) + case d.Hours() < 24*31: + days := int(d.Hours() / 24) + return lang.LocalizePluralKey("x_days_ago", + fmt.Sprintf("%d days ago", days), days, + map[string]string{"days": strconv.Itoa(days)}) + case d.Hours() < 24*365: + months := int(d.Hours() / (24 * 31)) + return lang.LocalizePluralKey("x_months_ago", + fmt.Sprintf("%d months ago", months), months, + map[string]string{"months": strconv.Itoa(months)}) + default: + years := int(d.Hours() / (24 * 365)) + return lang.LocalizePluralKey("x_years_ago", + fmt.Sprintf("%d years ago", years), years, + map[string]string{"years": strconv.Itoa(years)}) + } +} + var BoldRichTextStyle = widget.RichTextStyle{TextStyle: fyne.TextStyle{Bold: true}, Inline: true} func MakeOpaque(c color.Color) color.Color { diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 05c90b6..f792e3b 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -446,6 +446,8 @@ func (t *Tracklist) doSortTracks() { t.intSort(func(tr *util.TrackListModel) int64 { return tr.Track().Size }) case ColumnPlays: t.intSort(func(tr *util.TrackListModel) int64 { return int64(tr.Track().PlayCount) }) + case ColumnLastPlayed: + 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 ColumnBPM: diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index 397a197..bf58318 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -61,6 +61,7 @@ const ( ColumnFavorite = "Favorite" ColumnRating = "Rating" ColumnPlays = "Plays" + ColumnLastPlayed = "LastPlayed" ColumnComment = "Comment" ColumnBPM = "BPM" ColumnBitrate = "Bitrate" @@ -87,6 +88,7 @@ var ( fav := lang.L("Fav.") rating := lang.L("Rating") plays := lang.L("Plays") + lastPlayed := lang.L("Last played") comment := lang.L("Comment") bpm := lang.L("BPM") bitrate := lang.L("Bit rate") @@ -104,6 +106,7 @@ var ( {Name: ColumnFavorite, Col: ListColumn{Text: " " + fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}}, {Name: ColumnRating, Col: ListColumn{Text: rating, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPlays, Col: ListColumn{Text: plays, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnLastPlayed, Col: ListColumn{Text: lastPlayed, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnComment, Col: ListColumn{Text: comment, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, @@ -121,6 +124,7 @@ var ( {Name: ColumnFavorite, Col: ListColumn{Text: fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}}, {Name: ColumnRating, Col: ListColumn{Text: rating, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnPlays, Col: ListColumn{Text: plays, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, + {Name: ColumnLastPlayed, Col: ListColumn{Text: lastPlayed, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnComment, Col: ListColumn{Text: comment, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, {Name: ColumnBPM, Col: ListColumn{Text: bpm, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, {Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}}, @@ -144,6 +148,10 @@ var ( playsColWidth := fyne.Max( widget.NewLabel("9999").MinSize().Width, widget.NewLabel(plays).MinSize().Width+sortIconWidth) + lastPlayedColWidth := fyne.Max( + widget.NewLabel(lang.LocalizePluralKey("x_minutes_ago", "59 minutes ago", 59, map[string]string{"minutes": "59"})).MinSize().Width, + widget.NewLabel(lastPlayed).MinSize().Width+sortIconWidth, + ) bpmColWidth := fyne.Max( widget.NewLabel(bpm+" ").MinSize().Width, widget.NewLabel("9999").MinSize().Width) @@ -154,10 +162,10 @@ var ( widget.NewLabel("99.9 MB").MinSize().Width, widget.NewLabel(size).MinSize().Width+sortIconWidth) - // #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, BPM, Bitrate, Size, Path - CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, -1} - // #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, BPM, Bitrate, Size, Path - ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, -1, bpmColWidth, bitrateColWidth, sizeColWidth, -1} + // #, 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} }) ) @@ -185,21 +193,22 @@ 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 - comment *ttwidget.Label - bpm *widget.Label - size *widget.Label - path *ttwidget.Label + 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 + path *ttwidget.Label // must be injected by extending widget setColVisibility func(int, bool) bool @@ -253,7 +262,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.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.path)) t.Content = container t.setColVisibility = func(colNum int, vis bool) bool { c := container.Objects[colNum].(*fyne.Container) @@ -281,7 +290,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.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.path) colHiddenPtrMap := map[int]*bool{ 2: &t.artist.Hidden, @@ -292,11 +301,12 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) 7: &t.favorite.Hidden, 8: &t.rating.Hidden, 9: &t.plays.Hidden, - 10: &t.comment.Hidden, - 11: &t.bpm.Hidden, - 12: &t.bitrate.Hidden, - 13: &t.size.Hidden, - 14: &t.path.Hidden, + 10: &t.lastPlayed.Hidden, + 11: &t.comment.Hidden, + 12: &t.bpm.Hidden, + 13: &t.bitrate.Hidden, + 14: &t.size.Hidden, + 15: &t.path.Hidden, } t.setColVisibility = func(colNum int, vis bool) bool { ptr, ok := colHiddenPtrMap[colNum] @@ -338,6 +348,7 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.rating.StarSize = 16 t.rating.OnRatingChanged = t.setTrackRating t.plays = util.NewTrailingAlignLabel() + t.lastPlayed = util.NewTruncatingLabel() t.comment = util.NewTruncatingTooltipLabel() t.comment.OnMouseIn = t.MouseIn t.comment.OnMouseOut = t.MouseOut @@ -405,6 +416,7 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) { t.dur.Text = util.SecondsToMMSS(tr.Duration.Seconds()) t.year.Text = strconv.Itoa(tr.Year) t.plays.Text = strconv.Itoa(int(tr.PlayCount)) + t.lastPlayed.Text = util.LastPlayedDisplayString(tr.LastPlayed) t.comment.Text = strings.ReplaceAll(tr.Comment, "\n", " ") t.comment.SetToolTip(tr.Comment) t.bpm.Text = strconv.Itoa(tr.BPM)