From c60727f92a4e55328dc77d440de4e708749d8fba Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 11 Jan 2025 17:32:33 -0800 Subject: [PATCH] migrate to Fyne 2.6 - WIP - updates for new threading model --- go.mod | 17 +++-- go.sum | 26 +++---- main.go | 10 +-- res/translations/en.json | 1 + ui/browsing/albumpage.go | 6 +- ui/browsing/artistpage.go | 101 +++++++++++++++----------- ui/browsing/favoritespage.go | 120 ++++++++++++++++--------------- ui/browsing/genrepage.go | 4 +- ui/browsing/playlistpage.go | 24 ++++--- ui/browsing/playlistspage.go | 6 +- ui/browsing/trackspage.go | 4 +- ui/mainwindow.go | 134 +++++++++++++++++++---------------- ui/util/thumbnailloader.go | 4 +- 13 files changed, 262 insertions(+), 195 deletions(-) diff --git a/go.mod b/go.mod index bbe92f3..d1d2eec 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/zalando/go-keyring v0.2.6 golang.org/x/net v0.25.0 golang.org/x/sys v0.26.0 - golang.org/x/text v0.16.0 + golang.org/x/text v0.21.0 ) require ( @@ -40,18 +40,27 @@ require ( github.com/go-text/render v0.2.0 // indirect github.com/go-text/typesetting v0.2.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect + github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rymdport/portal v0.3.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect +<<<<<<< HEAD github.com/stretchr/testify v1.9.0 // indirect +======= + github.com/stretchr/testify v1.10.0 // indirect +>>>>>>> 287a268 (migrate to Fyne 2.6 - WIP - updates for new threading model) github.com/yuin/goldmark v1.7.1 // indirect - golang.org/x/image v0.18.0 // indirect + golang.org/x/image v0.23.0 // indirect golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect +<<<<<<< HEAD +======= + golang.org/x/sys v0.27.0 // indirect +>>>>>>> 287a268 (migrate to Fyne 2.6 - WIP - updates for new threading model) gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace fyne.io/fyne/v2 v2.5.3 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20241221194247-e7d4715906d9 +replace fyne.io/fyne/v2 v2.5.3 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250112004415-0d8cb89956b0 diff --git a/go.sum b/go.sum index bc6d188..da0fe1b 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY03 github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c= github.com/dweymouth/fyne-tooltip v0.2.0 h1:6Zy3gryctuPoQfYf8Xp3tjenioebMt11NBGW/QXIvxE= github.com/dweymouth/fyne-tooltip v0.2.0/go.mod h1:zEgy7p9tSVIuy2GufFbOCoK3Q04zhyDPOotlU4G3Ma4= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20241221194247-e7d4715906d9 h1:JZHVRX7qExB7H9ZZ5L9HO1qKl1Ej9M75pwVUa69Qdrg= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20241221194247-e7d4715906d9/go.mod h1:0GOXKqyvNwk3DLmsFu9v0oYM0ZcD1ysGnlHCerKoAmo= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250112004415-0d8cb89956b0 h1:YoIIY7CcgUdlfaagC6C8ADwXu9BDJNbhJYQi6g/7xZY= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250112004415-0d8cb89956b0/go.mod h1:6/uEYg4FEhspAcWgsokutm9wFMHDNSYuEHCKTYWSho8= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -221,8 +221,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8= -github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -253,6 +253,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -308,8 +310,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/supersonic-app/go-mpv v0.1.0 h1:U+cCnLQxmpqx5mY6nMlC0J4uIdCCXUbAjpjS04XkFu8= github.com/supersonic-app/go-mpv v0.1.0/go.mod h1:1bQz6kBQumJopXEbkiqoLxIXLy7F7yWFBvknvpAtIC0= @@ -362,8 +364,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -506,8 +508,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -522,8 +524,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/main.go b/main.go index c1a7451..edf295a 100644 --- a/main.go +++ b/main.go @@ -81,9 +81,9 @@ func main() { go func() { defaultServer := myApp.ServerManager.GetDefaultServer() if defaultServer == nil { - mainWindow.Controller.PromptForFirstServer() + fyne.Do(mainWindow.Controller.PromptForFirstServer) } else { - mainWindow.Controller.DoConnectToServerWorkflow(defaultServer) + fyne.Do(func() { mainWindow.Controller.DoConnectToServerWorkflow(defaultServer) }) } }() @@ -98,12 +98,12 @@ func main() { // slightly hacky workaround for https://github.com/fyne-io/fyne/issues/4964 _, isWayland := ctx.(*driver.WaylandWindowContext) if runtime.GOOS == "linux" && !isWayland { + s := mainWindow.DesiredSize() go func() { time.Sleep(50 * time.Millisecond) - s := mainWindow.DesiredSize() - mainWindow.Window.Resize(s.Subtract(fyne.NewSize(4, 0))) + fyne.Do(func() { mainWindow.Window.Resize(s.Subtract(fyne.NewSize(4, 0))) }) time.Sleep(50 * time.Millisecond) - mainWindow.Window.Resize(s) // back to desired size + fyne.Do(func() { mainWindow.Window.Resize(s) }) // back to desired size }() } }) diff --git a/res/translations/en.json b/res/translations/en.json index 6dbaefc..3e4bd6d 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -18,6 +18,7 @@ "All Tracks": "All Tracks", "Alt. URL": "Alt. URL", "An error occurred adding tracks to the playlist": "An error occurred adding tracks to the playlist", + "An error occurred updating the playlist": "An error occurred updating the playlist", "Are you sure you want to delete the server": "Are you sure you want to delete the server", "Artist": "Artist", "Artist (A-Z)": "Artist (A-Z)", diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index c2bb553..182ca98 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -316,8 +316,10 @@ func (a *AlbumPageHeader) Update(album *mediaprovider.AlbumWithTracks, im *backe go func() { if cover, err := im.GetCoverThumbnail(album.CoverArtID); err == nil { - a.cover.SetImage(cover, true) - a.cover.Refresh() + fyne.Do(func() { + a.cover.SetImage(cover, true) + a.cover.Refresh() + }) } else { log.Printf("error fetching cover: %v", err) } diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 8b8c793..fac28cf 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -174,8 +174,10 @@ func (a *ArtistPage) playArtistRadio() { go func() { err := a.pm.PlaySimilarSongs(a.artistID) if err != nil { - log.Println("error playing similar songs: %v", err) - a.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play artist radio")) + fyne.Do(func() { + log.Println("error playing similar songs: %v", err) + a.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play artist radio")) + }) } }() } @@ -246,19 +248,23 @@ func (a *ArtistPage) load() { if a.disposed { return } - a.artistInfo = artist - a.header.Update(artist, a.im) - if a.activeView == 0 { - a.showAlbumGrid(false /*reSort*/) - } else { - a.showTopTracks() - } + + fyne.Do(func() { + a.artistInfo = artist + a.header.Update(artist, a.im) + if a.activeView == 0 { + a.showAlbumGrid(false /*reSort*/) + } else { + a.showTopTracks() + } + }) + info, err := a.mp.GetArtistInfo(a.artistID) if err != nil { log.Printf("Failed to get artist info: %s", err.Error()) } if !a.disposed { - a.header.UpdateInfo(info) + fyne.Do(func() { a.header.UpdateInfo(info) }) } } @@ -287,7 +293,15 @@ func (a *ArtistPage) showAlbumGrid(reSort bool) { a.container.Objects[0].Refresh() } +// should be called asynchronously func (a *ArtistPage) showTopTracks() { + updated := false + updatePage := func() { + a.sortButton.Hide() + a.container.Objects[0].(*fyne.Container).Objects[0] = a.tracklistCtr + a.container.Objects[0].Refresh() + } + if a.tracklistCtr == nil { if a.artistInfo == nil { // page not loaded yet or invalid artist @@ -302,33 +316,38 @@ func (a *ArtistPage) showTopTracks() { if a.disposed { return } - var tl *widgets.Tracklist - if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil { - tl = t.(*widgets.Tracklist) - tl.Reset() - tl.SetTracks(ts) - } else { - tl = widgets.NewTracklist(ts, a.im, false) - } - tl.Options = widgets.TracklistOptions{AutoNumber: true} - _, canRate := a.mp.(mediaprovider.SupportsRating) - _, canShare := a.mp.(mediaprovider.SupportsSharing) - tl.Options.DisableRating = !canRate - tl.Options.DisableSharing = !canShare - tl.SetVisibleColumns(a.cfg.TracklistColumns) - tl.SetSorting(a.trackSort) - tl.OnVisibleColumnsChanged = func(cols []string) { - a.cfg.TracklistColumns = cols - } - tl.SetNowPlaying(a.nowPlayingID) - a.contr.ConnectTracklistActions(tl) - a.tracklistCtr = container.New( - &layout.CustomPaddedLayout{LeftPadding: 15, RightPadding: 15, BottomPadding: 10}, - tl) + updated = true // mark that updatePage() will be called here + fyne.Do(func() { + var tl *widgets.Tracklist + if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil { + tl = t.(*widgets.Tracklist) + tl.Reset() + tl.SetTracks(ts) + } else { + tl = widgets.NewTracklist(ts, a.im, false) + } + tl.Options = widgets.TracklistOptions{AutoNumber: true} + _, canRate := a.mp.(mediaprovider.SupportsRating) + _, canShare := a.mp.(mediaprovider.SupportsSharing) + tl.Options.DisableRating = !canRate + tl.Options.DisableSharing = !canShare + tl.SetVisibleColumns(a.cfg.TracklistColumns) + tl.SetSorting(a.trackSort) + tl.OnVisibleColumnsChanged = func(cols []string) { + a.cfg.TracklistColumns = cols + } + tl.SetNowPlaying(a.nowPlayingID) + a.contr.ConnectTracklistActions(tl) + a.tracklistCtr = container.New( + &layout.CustomPaddedLayout{LeftPadding: 15, RightPadding: 15, BottomPadding: 10}, + tl) + updatePage() + }) + } + + if !updated { + fyne.Do(updatePage) } - a.sortButton.Hide() - a.container.Objects[0].(*fyne.Container).Objects[0] = a.tracklistCtr - a.container.Objects[0].Refresh() } func (a *ArtistPage) onViewChange(num int) { @@ -512,10 +531,12 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) { if a.artistImage.HaveImage() { _ = a.artistPage.im.RefreshCachedArtistImageIfExpired(a.artistID, info.ImageURL) } else { - im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.ImageURL) - if err == nil { - a.artistImage.SetImage(im, true /*tappable*/) - } + go func() { + im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.ImageURL) + if err == nil { + fyne.Do(func() { a.artistImage.SetImage(im, true /*tappable*/) }) + } + }() } } } diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index d0b1f87..2c0f278 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -207,22 +207,24 @@ func (a *FavoritesPage) Reload() { if a.disposed { return } - if tr := a.tracklistOrNil(); tr != nil { - // refresh favorite songs view - tr.SetTracks(starred.Tracks) - if a.toggleBtns.ActivatedButtonIndex() == 2 { - // favorite songs view is visible - tr.Refresh() + fyne.Do(func() { + if tr := a.tracklistOrNil(); tr != nil { + // refresh favorite songs view + tr.SetTracks(starred.Tracks) + if a.toggleBtns.ActivatedButtonIndex() == 2 { + // favorite songs view is visible + tr.Refresh() + } } - } - if a.artistGrid != nil { - // refresh favorite artists view - a.artistGrid.ResetFixed(buildArtistGridViewModel(starred.Artists)) - if a.toggleBtns.ActivatedButtonIndex() == 1 { - // favorite artists view is visible - a.artistGrid.Refresh() + if a.artistGrid != nil { + // refresh favorite artists view + a.artistGrid.ResetFixed(buildArtistGridViewModel(starred.Artists)) + if a.toggleBtns.ActivatedButtonIndex() == 1 { + // favorite artists view is visible + a.artistGrid.Refresh() + } } - } + }) }() } } @@ -354,23 +356,25 @@ func (a *FavoritesPage) onShowFavoriteArtists() { if a.disposed { return } - model := buildArtistGridViewModel(fav.Artists) - if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil { - a.artistGrid = g.(*widgets.GridView) - a.artistGrid.Placeholder = myTheme.ArtistIcon - a.artistGrid.ResetFixed(model) - } else { - a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon) - } - canShareArtists := false - if r, canShare := a.mp.(mediaprovider.SupportsSharing); canShare { - canShareArtists = r.CanShareArtists() - } - a.artistGrid.DisableSharing = !canShareArtists - a.contr.ConnectArtistGridActions(a.artistGrid) - a.container.Objects[0] = a.artistGrid - a.Refresh() - a.pendingViewSwitch = false + fyne.Do(func() { + model := buildArtistGridViewModel(fav.Artists) + if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil { + a.artistGrid = g.(*widgets.GridView) + a.artistGrid.Placeholder = myTheme.ArtistIcon + a.artistGrid.ResetFixed(model) + } else { + a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon) + } + canShareArtists := false + if r, canShare := a.mp.(mediaprovider.SupportsSharing); canShare { + canShareArtists = r.CanShareArtists() + } + a.artistGrid.DisableSharing = !canShareArtists + a.contr.ConnectArtistGridActions(a.artistGrid) + a.container.Objects[0] = a.artistGrid + a.Refresh() + a.pendingViewSwitch = false + }) }() } else { a.container.Objects[0] = a.artistGrid @@ -419,32 +423,34 @@ func (a *FavoritesPage) onShowFavoriteSongs() { if a.disposed { return } - var tracklist *widgets.Tracklist - if tl := a.pool.Obtain(util.WidgetTypeTracklist); tl != nil { - tracklist = tl.(*widgets.Tracklist) - tracklist.Reset() - tracklist.SetTracks(fav.Tracks) - } else { - tracklist = widgets.NewTracklist(fav.Tracks, a.im, false) - } - tracklist.Options = widgets.TracklistOptions{AutoNumber: true} - _, canRate := a.mp.(mediaprovider.SupportsRating) - _, canShare := a.mp.(mediaprovider.SupportsSharing) - tracklist.Options.DisableRating = !canRate - tracklist.Options.DisableSharing = !canShare - tracklist.SetVisibleColumns(a.cfg.TracklistColumns) - tracklist.SetSorting(a.trackSort) - tracklist.OnVisibleColumnsChanged = func(cols []string) { - a.cfg.TracklistColumns = cols - } - tracklist.SetNowPlaying(a.nowPlayingID) - a.contr.ConnectTracklistActions(tracklist) - a.tracklistCtr = container.New( - &layout.CustomPaddedLayout{LeftPadding: 15, RightPadding: 15, TopPadding: 5, BottomPadding: 15}, - tracklist) - a.container.Objects[0] = a.tracklistCtr - a.Refresh() - a.pendingViewSwitch = false + fyne.Do(func() { + var tracklist *widgets.Tracklist + if tl := a.pool.Obtain(util.WidgetTypeTracklist); tl != nil { + tracklist = tl.(*widgets.Tracklist) + tracklist.Reset() + tracklist.SetTracks(fav.Tracks) + } else { + tracklist = widgets.NewTracklist(fav.Tracks, a.im, false) + } + tracklist.Options = widgets.TracklistOptions{AutoNumber: true} + _, canRate := a.mp.(mediaprovider.SupportsRating) + _, canShare := a.mp.(mediaprovider.SupportsSharing) + tracklist.Options.DisableRating = !canRate + tracklist.Options.DisableSharing = !canShare + tracklist.SetVisibleColumns(a.cfg.TracklistColumns) + tracklist.SetSorting(a.trackSort) + tracklist.OnVisibleColumnsChanged = func(cols []string) { + a.cfg.TracklistColumns = cols + } + tracklist.SetNowPlaying(a.nowPlayingID) + a.contr.ConnectTracklistActions(tracklist) + a.tracklistCtr = container.New( + &layout.CustomPaddedLayout{LeftPadding: 15, RightPadding: 15, TopPadding: 5, BottomPadding: 15}, + tracklist) + a.container.Objects[0] = a.tracklistCtr + a.Refresh() + a.pendingViewSwitch = false + }) }() } else { a.container.Objects[0] = a.tracklistCtr diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 27064ed..8ef1c6b 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -65,7 +65,9 @@ func (g *genrePageAdapter) ActionButton() *widget.Button { err := g.pm.PlayRandomSongs(g.genre) if err != nil { log.Println("error playing random tracks: %v", err) - g.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play random tracks")) + fyne.Do(func() { + g.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play random tracks")) + }) } }() } diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index d0358a2..5a7c378 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -193,20 +193,28 @@ func (a *PlaylistPage) doSetNewTrackOrder(ids []string, newPos int) { } } newTracks := sharedutil.ReorderItems(a.tracks, idxs, newPos) + ids = sharedutil.TracksToIDs(newTracks) + // we can't block the UI waiting for the server so assume it will succeed go func() { - ids = sharedutil.TracksToIDs(newTracks) if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil { log.Printf("error updating playlist: %s", err.Error()) + fyne.Do(func() { + a.contr.ToastProvider.ShowErrorToast( + lang.L("An error occurred updating the playlist"), + ) + }) + } else { + fyne.Do(func() { + renumberTracks(newTracks) + // force-switch back to unsorted view to show new track order + a.tracklist.SetSorting(widgets.TracklistSort{}) + a.tracklist.SetTracks(newTracks) + a.tracklist.UnselectAll() + a.tracks = newTracks + }) } }() - - renumberTracks(newTracks) - // force-switch back to unsorted view to show new track order - a.tracklist.SetSorting(widgets.TracklistSort{}) - a.tracklist.SetTracks(newTracks) - a.tracklist.UnselectAll() - a.tracks = newTracks } func (a *PlaylistPage) onRemoveSelectedFromPlaylist() { diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index 6e66604..52c0288 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -150,7 +150,9 @@ func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) { log.Printf("error loading playlist: %s", err.Error()) return } - a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Tracks)) + fyne.Do(func() { + a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Tracks)) + }) }() } a.gridView.OnDownload = func(id string) { @@ -160,7 +162,7 @@ func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) { log.Printf("error loading playlist: %s", err.Error()) return } - a.contr.ShowDownloadDialog(pl.Tracks, pl.Name) + fyne.Do(func() { a.contr.ShowDownloadDialog(pl.Tracks, pl.Name) }) }() } } diff --git a/ui/browsing/trackspage.go b/ui/browsing/trackspage.go index 6702ead..196dc07 100644 --- a/ui/browsing/trackspage.go +++ b/ui/browsing/trackspage.go @@ -211,7 +211,9 @@ func (t *TracksPage) playRandomSongs() { err := t.contr.App.PlaybackManager.PlayRandomSongs("") if err != nil { log.Println("error playing random tracks: %v", err) - t.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play random tracks")) + fyne.Do(func() { + t.contr.ToastProvider.ShowErrorToast(lang.L("Unable to play random tracks")) + }) } }() } diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 154757c..1c18707 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -88,48 +88,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller) app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) { - if item == nil { - m.Window.SetTitle(displayAppName) - return - } - meta := item.Metadata() - artistDisp := "" - if tr, ok := item.(*mediaprovider.Track); ok { - artistDisp = " – " + strings.Join(tr.ArtistNames, ", ") - } - m.Window.SetTitle(fmt.Sprintf("%s%s · %s", meta.Name, artistDisp, displayAppName)) - if m.App.Config.Application.ShowTrackChangeNotification { - if runtime.GOOS == "linux" { - if notifySend, err := exec.LookPath("notify-send"); err == nil { - go func() { - args := []string{ - "--app-name", "supersonic", - "--urgency", "low", - "--expire-time", "10000", - // replace previous notification - "--hint", "string:x-canonical-private-synchronous:supersonic-track", - meta.Name, strings.TrimPrefix(artistDisp, " – "), - } - - app.ImageManager.GetCoverThumbnail(meta.CoverArtID) - if path, err := app.ImageManager.GetCoverArtPath(meta.CoverArtID); err == nil { - args = append([]string{"--icon", path}, args...) - } - - if out, err := exec.Command(notifySend, args...).CombinedOutput(); err != nil { - log.Printf("notify-send error: %s %s", strings.TrimSpace(string(out)), err) - } - }() - return - } - } - - // TODO: Once Fyne issue #2935 is resolved, show album cover - fyne.CurrentApp().SendNotification(&fyne.Notification{ - Title: meta.Name, - Content: strings.TrimPrefix(artistDisp, " – "), - }) - } + fyne.Do(func() { m.UpdateOnTrackChange(item) }) }) app.ServerManager.OnServerConnected(func() { go m.RunOnServerConnectedTasks(app, displayAppName) @@ -200,6 +159,51 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, return m } +func (m *MainWindow) UpdateOnTrackChange(item mediaprovider.MediaItem) { + if item == nil { + m.Window.SetTitle(res.DisplayName) + return + } + meta := item.Metadata() + artistDisp := "" + if tr, ok := item.(*mediaprovider.Track); ok { + artistDisp = " – " + strings.Join(tr.ArtistNames, ", ") + } + m.Window.SetTitle(fmt.Sprintf("%s%s · %s", meta.Name, artistDisp, res.DisplayName)) + if m.App.Config.Application.ShowTrackChangeNotification { + if runtime.GOOS == "linux" { + if notifySend, err := exec.LookPath("notify-send"); err == nil { + go func() { + args := []string{ + "--app-name", "supersonic", + "--urgency", "low", + "--expire-time", "10000", + // replace previous notification + "--hint", "string:x-canonical-private-synchronous:supersonic-track", + meta.Name, strings.TrimPrefix(artistDisp, " – "), + } + + app.ImageManager.GetCoverThumbnail(meta.CoverArtID) + if path, err := app.ImageManager.GetCoverArtPath(meta.CoverArtID); err == nil { + args = append([]string{"--icon", path}, args...) + } + + if out, err := exec.Command(notifySend, args...).CombinedOutput(); err != nil { + log.Printf("notify-send error: %s %s", strings.TrimSpace(string(out)), err) + } + }() + return + } + } + + // TODO: Once Fyne issue #2935 is resolved, show album cover + fyne.CurrentApp().SendNotification(&fyne.Notification{ + Title: meta.Name, + Content: artistDisp, + }) + } +} + func (m *MainWindow) DesiredSize() fyne.Size { w := float32(m.App.Config.Application.WindowWidth) if w <= 1 { @@ -229,10 +233,6 @@ func (m *MainWindow) StartupPage() controller.Route { func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName string) { time.Sleep(1 * time.Millisecond) // ensure this runs after sync tasks - m.BrowsingPane.EnableNavigationButtons() - m.Router.NavigateTo(m.StartupPage()) - _, canRate := m.App.ServerManager.Server.(mediaprovider.SupportsRating) - m.BottomPanel.NowPlaying.DisableRating = !canRate if app.Config.Application.SavePlayQueue { go func() { @@ -242,12 +242,19 @@ func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName }() } - _, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider) - if supportsRadio { - m.radioBtn.Show() - } else { - m.radioBtn.Hide() - } + fyne.Do(func() { + m.BrowsingPane.EnableNavigationButtons() + m.Router.NavigateTo(m.StartupPage()) + _, canRate := m.App.ServerManager.Server.(mediaprovider.SupportsRating) + m.BottomPanel.NowPlaying.DisableRating = !canRate + + _, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider) + if supportsRadio { + m.radioBtn.Show() + } else { + m.radioBtn.Hide() + } + }) m.App.SaveConfigFile() @@ -257,17 +264,20 @@ func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName } // check if launching new version, else if found available update on startup - if l := app.Config.Application.LastLaunchedVersion; app.VersionTag() != l { - if !app.IsFirstLaunch() { - m.ShowWhatsNewDialog() + fyne.Do(func() { + if l := app.Config.Application.LastLaunchedVersion; app.VersionTag() != l { + if !app.IsFirstLaunch() { + m.ShowWhatsNewDialog() + } + m.App.Config.Application.LastLaunchedVersion = app.VersionTag() + } else if t := app.UpdateChecker.VersionTagFound(); t != "" && t != app.Config.Application.LastCheckedVersion { + if t != app.VersionTag() { + m.ShowNewVersionDialog(displayAppName, t) + } + m.App.Config.Application.LastCheckedVersion = t } - m.App.Config.Application.LastLaunchedVersion = app.VersionTag() - } else if t := app.UpdateChecker.VersionTagFound(); t != "" && t != app.Config.Application.LastCheckedVersion { - if t != app.VersionTag() { - m.ShowNewVersionDialog(displayAppName, t) - } - m.App.Config.Application.LastCheckedVersion = t - } + }) + // register callback for the ongoing periodic update check m.App.UpdateChecker.OnUpdatedVersionFound = func() { t := m.App.UpdateChecker.VersionTagFound() diff --git a/ui/util/thumbnailloader.go b/ui/util/thumbnailloader.go index f1fc063..c792adb 100644 --- a/ui/util/thumbnailloader.go +++ b/ui/util/thumbnailloader.go @@ -4,6 +4,8 @@ import ( "context" "image" "log" + + "fyne.io/fyne/v2" ) // ThumbnailLoader is a utility type that exposes a single API to load @@ -50,7 +52,7 @@ func (i *ThumbnailLoader) Load(coverID string) { if err != nil { log.Printf("Error loading cover image: %s", err.Error()) } else { - i.callOnLoaded(img) + fyne.Do(func() { i.callOnLoaded(img) }) } i.prevLoadCancel() // Done. Release resources associated with un-cancelled ctx })