diff --git a/go.mod b/go.mod index e20b4539..e2c30b9a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d github.com/zalando/go-keyring v0.2.1 golang.org/x/net v0.25.0 - golang.org/x/text v0.16.0 + golang.org/x/text v0.21.0 ) require ( @@ -39,19 +39,20 @@ 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 - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/testify v1.10.0 // indirect 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 - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.27.0 // indirect 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 76a574ad..5e445583 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= @@ -220,8 +220,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= @@ -252,6 +252,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= @@ -295,8 +297,9 @@ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqd github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -306,8 +309,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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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= @@ -360,8 +363,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= @@ -504,8 +507,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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.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= @@ -520,8 +523,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 d41eedfc..628705f6 100644 --- a/main.go +++ b/main.go @@ -81,28 +81,28 @@ 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) }) } }() // slightly hacky workaround for https://github.com/fyne-io/fyne/issues/4964 if runtime.GOOS == "linux" { workaroundWindowSize := sync.OnceFunc(func() { - go func() { - isWayland := false - mainWindow.Window.(driver.NativeWindow).RunNative(func(ctx any) { - _, isWayland = ctx.(*driver.WaylandWindowContext) - }) - if !isWayland { + isWayland := false + mainWindow.Window.(driver.NativeWindow).RunNative(func(ctx any) { + _, isWayland = ctx.(*driver.WaylandWindowContext) + }) + if !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 + }() + } }) fyneApp.Lifecycle().SetOnEnteredForeground(func() { workaroundWindowSize() diff --git a/res/translations/en.json b/res/translations/en.json index 6dbaefc5..3e4bd6d5 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 c2bb5534..182ca982 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 e2403a10..4528de41 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")) + }) } }() } @@ -247,19 +249,22 @@ func (a *ArtistPage) load() { return } a.artistInfo = artist - a.header.Update(artist) - 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.Update(artist) + 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) + } + }) } func (a *ArtistPage) showAlbumGrid(reSort bool) { @@ -287,7 +292,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 +315,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) { diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index d0b1f872..2c0f2780 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 27064ed0..8ef1c6b9 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 d0358a2a..5a7c3780 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 6e66604d..52c02883 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 6702ead9..196dc07b 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 f6fbaa98..77ce9328 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -87,23 +87,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 { - // TODO: Once Fyne issue #2935 is resolved, show album cover - fyne.CurrentApp().SendNotification(&fyne.Notification{ - Title: meta.Name, - Content: artistDisp, - }) - } + fyne.Do(func() { m.UpdateOnTrackChange(item) }) }) app.ServerManager.OnServerConnected(func() { go m.RunOnServerConnectedTasks(app, displayAppName) @@ -174,6 +158,26 @@ 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 { + // 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 { @@ -203,10 +207,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() { @@ -216,12 +216,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() @@ -231,17 +238,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() - } - 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) + 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.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 f1fc0636..c792adb4 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 })