more pooling of widgets, rename and rework a few things

This commit is contained in:
Drew Weymouth
2023-07-07 17:11:03 -07:00
parent c6c33ecfed
commit d76fc41230
10 changed files with 257 additions and 128 deletions
+38 -23
View File
@@ -25,6 +25,7 @@ type AlbumPage struct {
albumPageState albumPageState
disposed bool
header *AlbumPageHeader header *AlbumPageHeader
tracks []*mediaprovider.Track tracks []*mediaprovider.Track
tracklist *widgets.Tracklist tracklist *widgets.Tracklist
@@ -33,32 +34,32 @@ type AlbumPage struct {
} }
type albumPageState struct { type albumPageState struct {
albumID string albumID string
sort widgets.TracklistSort sort widgets.TracklistSort
cfg *backend.AlbumPageConfig cfg *backend.AlbumPageConfig
widgetCache *util.WidgetCache pool *util.WidgetPool
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
pm *backend.PlaybackManager pm *backend.PlaybackManager
im *backend.ImageManager im *backend.ImageManager
contr *controller.Controller contr *controller.Controller
} }
func NewAlbumPage( func NewAlbumPage(
albumID string, albumID string,
cfg *backend.AlbumPageConfig, cfg *backend.AlbumPageConfig,
cache *util.WidgetCache, pool *util.WidgetPool,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
mp mediaprovider.MediaProvider, mp mediaprovider.MediaProvider,
im *backend.ImageManager, im *backend.ImageManager,
contr *controller.Controller, contr *controller.Controller,
) *AlbumPage { ) *AlbumPage {
return newAlbumPage(albumID, cfg, cache, pm, mp, im, contr, widgets.TracklistSort{}) return newAlbumPage(albumID, cfg, pool, pm, mp, im, contr, widgets.TracklistSort{})
} }
func newAlbumPage( func newAlbumPage(
albumID string, albumID string,
cfg *backend.AlbumPageConfig, cfg *backend.AlbumPageConfig,
cache *util.WidgetCache, pool *util.WidgetPool,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
mp mediaprovider.MediaProvider, mp mediaprovider.MediaProvider,
im *backend.ImageManager, im *backend.ImageManager,
@@ -67,24 +68,29 @@ func newAlbumPage(
) *AlbumPage { ) *AlbumPage {
a := &AlbumPage{ a := &AlbumPage{
albumPageState: albumPageState{ albumPageState: albumPageState{
albumID: albumID, albumID: albumID,
cfg: cfg, cfg: cfg,
widgetCache: cache, pool: pool,
pm: pm, pm: pm,
mp: mp, mp: mp,
im: im, im: im,
contr: contr, contr: contr,
}, },
} }
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
if h := a.widgetCache.Obtain(util.WidgetTypeAlbumPageHeader); h != nil { if h := a.pool.Obtain(util.WidgetTypeAlbumPageHeader); h != nil {
a.header = h.(*AlbumPageHeader) a.header = h.(*AlbumPageHeader)
a.header.Clear() a.header.Clear()
} else { } else {
a.header = NewAlbumPageHeader(a) a.header = NewAlbumPageHeader(a)
} }
a.header.page = a a.header.page = a
a.tracklist = widgets.NewTracklist(nil) if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil {
a.tracklist = t.(*widgets.Tracklist)
a.tracklist.Reset()
} else {
a.tracklist = widgets.NewTracklist(nil)
}
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns) a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
a.tracklist.SetSorting(sort) a.tracklist.SetSorting(sort)
a.tracklist.OnVisibleColumnsChanged = func(cols []string) { a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
@@ -105,9 +111,12 @@ func (a *AlbumPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (a *AlbumPage) Save() SavedPage { func (a *AlbumPage) Save() SavedPage {
a.disposed = true
s := a.albumPageState s := a.albumPageState
s.sort = a.tracklist.Sorting() s.sort = a.tracklist.Sorting()
a.widgetCache.Release(util.WidgetTypeAlbumPageHeader, a.header) a.header.page = nil
a.pool.Release(util.WidgetTypeAlbumPageHeader, a.header)
a.pool.Release(util.WidgetTypeTracklist, a.tracklist)
return &s return &s
} }
@@ -144,6 +153,9 @@ func (a *AlbumPage) load() {
log.Printf("Failed to get album: %s", err.Error()) log.Printf("Failed to get album: %s", err.Error())
return return
} }
if a.disposed {
return
}
a.header.Update(album, a.im) a.header.Update(album, a.im)
a.tracklist.Options = widgets.TracklistOptions{ a.tracklist.Options = widgets.TracklistOptions{
ShowDiscNumber: album.Tracks[0].DiscNumber != album.Tracks[len(album.Tracks)-1].DiscNumber, ShowDiscNumber: album.Tracks[0].DiscNumber != album.Tracks[len(album.Tracks)-1].DiscNumber,
@@ -279,6 +291,7 @@ func (a *AlbumPageHeader) Clear() {
a.miscLabel.SetText("") a.miscLabel.SetText("")
a.toggleFavButton.IsFavorited = false a.toggleFavButton.IsFavorited = false
a.cover.Image.Image = nil a.cover.Image.Image = nil
a.cover.Refresh()
} }
func (a *AlbumPageHeader) toggleFavorited() { func (a *AlbumPageHeader) toggleFavorited() {
@@ -292,7 +305,9 @@ func (a *AlbumPageHeader) showPopUpCover() {
log.Printf("error getting full size album cover: %s", err.Error()) log.Printf("error getting full size album cover: %s", err.Error())
return return
} }
a.page.contr.ShowPopUpImage(cover) if a.page != nil {
a.page.contr.ShowPopUpImage(cover)
}
} }
func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string { func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
@@ -308,5 +323,5 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
} }
func (s *albumPageState) Restore() Page { func (s *albumPageState) Restore() Page {
return newAlbumPage(s.albumID, s.cfg, s.widgetCache, s.pm, s.mp, s.im, s.contr, s.sort) return newAlbumPage(s.albumID, s.cfg, s.pool, s.pm, s.mp, s.im, s.contr, s.sort)
} }
+41 -24
View File
@@ -28,18 +28,19 @@ type artistPageState struct {
activeView int activeView int
trackSort widgets.TracklistSort trackSort widgets.TracklistSort
widgetCache *util.WidgetCache pool *util.WidgetPool
cfg *backend.ArtistPageConfig cfg *backend.ArtistPageConfig
pm *backend.PlaybackManager pm *backend.PlaybackManager
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
im *backend.ImageManager im *backend.ImageManager
contr *controller.Controller contr *controller.Controller
} }
type ArtistPage struct { type ArtistPage struct {
widget.BaseWidget widget.BaseWidget
artistPageState artistPageState
disposed bool
artistInfo *mediaprovider.ArtistWithAlbums artistInfo *mediaprovider.ArtistWithAlbums
@@ -50,34 +51,34 @@ type ArtistPage struct {
container *fyne.Container container *fyne.Container
} }
func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, cache *util.WidgetCache, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage { func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pool *util.WidgetPool, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
activeView := 0 activeView := 0
if cfg.InitialView == "Top Tracks" { if cfg.InitialView == "Top Tracks" {
activeView = 1 activeView = 1
} }
return newArtistPage(artistID, cfg, cache, pm, mp, im, contr, activeView, widgets.TracklistSort{}) return newArtistPage(artistID, cfg, pool, pm, mp, im, contr, activeView, widgets.TracklistSort{})
} }
func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, cache *util.WidgetCache, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int, sort widgets.TracklistSort) *ArtistPage { func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pool *util.WidgetPool, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int, sort widgets.TracklistSort) *ArtistPage {
a := &ArtistPage{artistPageState: artistPageState{ a := &ArtistPage{artistPageState: artistPageState{
artistID: artistID, artistID: artistID,
cfg: cfg, cfg: cfg,
widgetCache: cache, pool: pool,
pm: pm, pm: pm,
mp: mp, mp: mp,
im: im, im: im,
contr: contr, contr: contr,
activeView: activeView, activeView: activeView,
trackSort: sort, trackSort: sort,
}} }}
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
if h := a.widgetCache.Obtain(util.WidgetTypeArtistPageHeader); h != nil { if h := a.pool.Obtain(util.WidgetTypeArtistPageHeader); h != nil {
a.header = h.(*ArtistPageHeader) a.header = h.(*ArtistPageHeader)
a.header.Clear()
} else { } else {
a.header = NewArtistPageHeader(a) a.header = NewArtistPageHeader(a)
} }
a.header.artistPage = a a.header.artistPage = a
a.header.Clear()
if img, ok := im.GetCachedArtistImage(artistID); ok { if img, ok := im.GetCachedArtistImage(artistID); ok {
a.header.artistImage.SetImage(img, true /*tappable*/) a.header.artistImage.SetImage(img, true /*tappable*/)
} }
@@ -120,11 +121,14 @@ func (a *ArtistPage) Reload() {
} }
func (a *ArtistPage) Save() SavedPage { func (a *ArtistPage) Save() SavedPage {
a.disposed = true
s := a.artistPageState s := a.artistPageState
if a.tracklistCtr != nil { if a.tracklistCtr != nil {
s.trackSort = a.tracklistCtr.Objects[0].(*widgets.Tracklist).Sorting() tl := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
s.trackSort = tl.Sorting()
a.pool.Release(util.WidgetTypeTracklist, tl)
} }
a.widgetCache.Release(util.WidgetTypeArtistPageHeader, a.header) a.pool.Release(util.WidgetTypeArtistPageHeader, a.header)
return &s return &s
} }
@@ -150,6 +154,9 @@ func (a *ArtistPage) load() {
log.Printf("Failed to get artist: %s", err.Error()) log.Printf("Failed to get artist: %s", err.Error())
return return
} }
if a.disposed {
return
}
a.artistInfo = artist a.artistInfo = artist
a.header.Update(artist) a.header.Update(artist)
if a.activeView == 0 { if a.activeView == 0 {
@@ -198,7 +205,17 @@ func (a *ArtistPage) showTopTracks() {
log.Printf("error getting top songs: %s", err.Error()) log.Printf("error getting top songs: %s", err.Error())
return return
} }
tl := widgets.NewTracklist(ts) 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)
}
tl.Options = widgets.TracklistOptions{AutoNumber: true} tl.Options = widgets.TracklistOptions{AutoNumber: true}
tl.SetVisibleColumns(a.cfg.TracklistColumns) tl.SetVisibleColumns(a.cfg.TracklistColumns)
tl.SetSorting(a.trackSort) tl.SetSorting(a.trackSort)
@@ -237,7 +254,7 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (s *artistPageState) Restore() Page { func (s *artistPageState) Restore() Page {
return newArtistPage(s.artistID, s.cfg, s.widgetCache, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort) return newArtistPage(s.artistID, s.cfg, s.pool, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort)
} }
const artistBioNotAvailableStr = "Artist biography not available." const artistBioNotAvailableStr = "Artist biography not available."
+28 -3
View File
@@ -23,11 +23,13 @@ type FavoritesPage struct {
widget.BaseWidget widget.BaseWidget
cfg *backend.FavoritesPageConfig cfg *backend.FavoritesPageConfig
pool *util.WidgetPool
contr *controller.Controller contr *controller.Controller
pm *backend.PlaybackManager pm *backend.PlaybackManager
im *backend.ImageManager im *backend.ImageManager
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
disposed bool
trackSort widgets.TracklistSort trackSort widgets.TracklistSort
filter mediaprovider.AlbumFilter filter mediaprovider.AlbumFilter
searchText string searchText string
@@ -45,10 +47,11 @@ type FavoritesPage struct {
container *fyne.Container container *fyne.Container
} }
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage { func NewFavoritesPage(cfg *backend.FavoritesPageConfig, pool *util.WidgetPool, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage {
a := &FavoritesPage{ a := &FavoritesPage{
filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true}, filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true},
cfg: cfg, cfg: cfg,
pool: pool,
contr: contr, contr: contr,
pm: pm, pm: pm,
mp: mp, mp: mp,
@@ -99,6 +102,7 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
a := &FavoritesPage{ a := &FavoritesPage{
cfg: saved.cfg, cfg: saved.cfg,
contr: saved.contr, contr: saved.contr,
pool: saved.pool,
pm: saved.pm, pm: saved.pm,
mp: saved.mp, mp: saved.mp,
im: saved.im, im: saved.im,
@@ -154,6 +158,9 @@ func (a *FavoritesPage) Reload() {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
} }
if a.disposed {
return
}
if a.tracklistCtr != nil { if a.tracklistCtr != nil {
// refresh favorite songs view // refresh favorite songs view
tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist) tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
@@ -176,9 +183,11 @@ func (a *FavoritesPage) Reload() {
} }
func (a *FavoritesPage) Save() SavedPage { func (a *FavoritesPage) Save() SavedPage {
a.disposed = true
sf := &savedFavoritesPage{ sf := &savedFavoritesPage{
cfg: a.cfg, cfg: a.cfg,
contr: a.contr, contr: a.contr,
pool: a.pool,
pm: a.pm, pm: a.pm,
mp: a.mp, mp: a.mp,
im: a.im, im: a.im,
@@ -191,7 +200,9 @@ func (a *FavoritesPage) Save() SavedPage {
sf.searchGridState = a.searchGrid.SaveToState() sf.searchGridState = a.searchGrid.SaveToState()
} }
if a.tracklistCtr != nil { if a.tracklistCtr != nil {
sf.trackSort = a.tracklistCtr.Objects[0].(*widgets.Tracklist).Sorting() tl := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
sf.trackSort = tl.Sorting()
a.pool.Release(util.WidgetTypeTracklist, tl)
} }
return sf return sf
} }
@@ -277,6 +288,9 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
} }
if a.disposed {
return
}
model := buildArtistGridViewModel(fav.Artists) model := buildArtistGridViewModel(fav.Artists)
a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon) a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon)
a.contr.ConnectArtistGridActions(a.artistGrid) a.contr.ConnectArtistGridActions(a.artistGrid)
@@ -325,7 +339,17 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
} }
tracklist := widgets.NewTracklist(fav.Tracks) 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)
}
tracklist.Options = widgets.TracklistOptions{AutoNumber: true} tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
tracklist.SetVisibleColumns(a.cfg.TracklistColumns) tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
tracklist.SetSorting(a.trackSort) tracklist.SetSorting(a.trackSort)
@@ -355,6 +379,7 @@ func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer {
type savedFavoritesPage struct { type savedFavoritesPage struct {
cfg *backend.FavoritesPageConfig cfg *backend.FavoritesPageConfig
contr *controller.Controller contr *controller.Controller
pool *util.WidgetPool
pm *backend.PlaybackManager pm *backend.PlaybackManager
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
im *backend.ImageManager im *backend.ImageManager
+13 -3
View File
@@ -38,6 +38,7 @@ type NowPlayingPage struct {
type nowPlayingPageState struct { type nowPlayingPageState struct {
contr *controller.Controller contr *controller.Controller
pool *util.WidgetPool
conf *backend.NowPlayingPageConfig conf *backend.NowPlayingPageConfig
pm *backend.PlaybackManager pm *backend.PlaybackManager
p *player.Player p *player.Player
@@ -46,18 +47,26 @@ type nowPlayingPageState struct {
func NewNowPlayingPage( func NewNowPlayingPage(
highlightedTrackID string, highlightedTrackID string,
contr *controller.Controller, contr *controller.Controller,
pool *util.WidgetPool,
conf *backend.NowPlayingPageConfig, conf *backend.NowPlayingPageConfig,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
p *player.Player, // TODO: once other player backends are supported (eg uPnP), refactor p *player.Player, // TODO: once other player backends are supported (eg uPnP), refactor
) *NowPlayingPage { ) *NowPlayingPage {
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm, p: p}} a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{
contr: contr, pool: pool, conf: conf, pm: pm, p: p,
}}
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
p.OnPaused(a.formatStatusLine) p.OnPaused(a.formatStatusLine)
p.OnPlaying(a.formatStatusLine) p.OnPlaying(a.formatStatusLine)
p.OnStopped(a.formatStatusLine) p.OnStopped(a.formatStatusLine)
a.tracklist = widgets.NewTracklist(nil) if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil {
a.tracklist = t.(*widgets.Tracklist)
a.tracklist.Reset()
} else {
a.tracklist = widgets.NewTracklist(nil)
}
a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.SetVisibleColumns(conf.TracklistColumns)
a.tracklist.OnVisibleColumnsChanged = func(cols []string) { a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
a.conf.TracklistColumns = cols a.conf.TracklistColumns = cols
@@ -90,6 +99,7 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (a *NowPlayingPage) Save() SavedPage { func (a *NowPlayingPage) Save() SavedPage {
a.pool.Release(util.WidgetTypeTracklist, a.tracklist)
nps := a.nowPlayingPageState nps := a.nowPlayingPageState
return &nps return &nps
} }
@@ -207,5 +217,5 @@ func (a *NowPlayingPage) load(highlightedTrackID string) {
} }
func (s *nowPlayingPageState) Restore() Page { func (s *nowPlayingPageState) Restore() Page {
return NewNowPlayingPage("", s.contr, s.conf, s.pm, s.p) return NewNowPlayingPage("", s.contr, s.pool, s.conf, s.pm, s.p)
} }
+35 -6
View File
@@ -24,6 +24,7 @@ type PlaylistPage struct {
playlistPageState playlistPageState
disposed bool
header *PlaylistPageHeader header *PlaylistPageHeader
tracklist *widgets.Tracklist tracklist *widgets.Tracklist
tracks []*mediaprovider.Track tracks []*mediaprovider.Track
@@ -35,6 +36,7 @@ type playlistPageState struct {
playlistID string playlistID string
conf *backend.PlaylistPageConfig conf *backend.PlaylistPageConfig
contr *controller.Controller contr *controller.Controller
widgetPool *util.WidgetPool
sm *backend.ServerManager sm *backend.ServerManager
pm *backend.PlaybackManager pm *backend.PlaybackManager
im *backend.ImageManager im *backend.ImageManager
@@ -44,27 +46,40 @@ type playlistPageState struct {
func NewPlaylistPage( func NewPlaylistPage(
playlistID string, playlistID string,
conf *backend.PlaylistPageConfig, conf *backend.PlaylistPageConfig,
pool *util.WidgetPool,
contr *controller.Controller, contr *controller.Controller,
sm *backend.ServerManager, sm *backend.ServerManager,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
im *backend.ImageManager, im *backend.ImageManager,
) *PlaylistPage { ) *PlaylistPage {
return newPlaylistPage(playlistID, conf, contr, sm, pm, im, widgets.TracklistSort{}) return newPlaylistPage(playlistID, conf, contr, pool, sm, pm, im, widgets.TracklistSort{})
} }
func newPlaylistPage( func newPlaylistPage(
playlistID string, playlistID string,
conf *backend.PlaylistPageConfig, conf *backend.PlaylistPageConfig,
contr *controller.Controller, contr *controller.Controller,
pool *util.WidgetPool,
sm *backend.ServerManager, sm *backend.ServerManager,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
im *backend.ImageManager, im *backend.ImageManager,
trackSort widgets.TracklistSort, trackSort widgets.TracklistSort,
) *PlaylistPage { ) *PlaylistPage {
a := &PlaylistPage{playlistPageState: playlistPageState{playlistID: playlistID, conf: conf, contr: contr, sm: sm, pm: pm, im: im}} a := &PlaylistPage{playlistPageState: playlistPageState{playlistID: playlistID, conf: conf, contr: contr, widgetPool: pool, sm: sm, pm: pm, im: im}}
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.header = NewPlaylistPageHeader(a) if h := a.widgetPool.Obtain(util.WidgetTypePlaylistPageHeader); h != nil {
a.tracklist = widgets.NewTracklist(nil) a.header = h.(*PlaylistPageHeader)
a.header.Clear()
} else {
a.header = NewPlaylistPageHeader(a)
}
a.header.page = a
if tl := a.widgetPool.Obtain(util.WidgetTypeTracklist); tl != nil {
a.tracklist = tl.(*widgets.Tracklist)
a.tracklist.Reset()
} else {
a.tracklist = widgets.NewTracklist(nil)
}
a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.SetVisibleColumns(conf.TracklistColumns)
a.tracklist.SetSorting(trackSort) a.tracklist.SetSorting(trackSort)
a.tracklist.OnVisibleColumnsChanged = func(cols []string) { a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
@@ -96,8 +111,11 @@ func (a *PlaylistPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (a *PlaylistPage) Save() SavedPage { func (a *PlaylistPage) Save() SavedPage {
a.disposed = true
p := a.playlistPageState p := a.playlistPageState
p.trackSort = a.tracklist.Sorting() p.trackSort = a.tracklist.Sorting()
p.widgetPool.Release(util.WidgetTypePlaylistPageHeader, a.header)
p.widgetPool.Release(util.WidgetTypeTracklist, a.tracklist)
return &p return &p
} }
@@ -134,6 +152,9 @@ func (a *PlaylistPage) load() {
log.Printf("Failed to get playlist: %s", err.Error()) log.Printf("Failed to get playlist: %s", err.Error())
return return
} }
if a.disposed {
return
}
renumberTracks(playlist.Tracks) renumberTracks(playlist.Tracks)
a.tracks = playlist.Tracks a.tracks = playlist.Tracks
a.tracklist.SetTracks(playlist.Tracks) a.tracklist.SetTracks(playlist.Tracks)
@@ -202,7 +223,7 @@ func (a *PlaylistPage) onRemoveSelectedFromPlaylist() {
} }
a.sm.Server.EditPlaylistTracks(a.playlistID, nil, idxs) a.sm.Server.EditPlaylistTracks(a.playlistID, nil, idxs)
a.tracklist.UnselectAll() a.tracklist.UnselectAll()
go a.Reload() a.Reload()
} }
type PlaylistPageHeader struct { type PlaylistPageHeader struct {
@@ -282,6 +303,14 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
return a return a
} }
func (a *PlaylistPageHeader) Clear() {
a.titleLabel.Segments[0].(*widget.TextSegment).Text = ""
a.createdAtLabel.Text = ""
a.descriptionLabel.Text = ""
a.ownerLabel.Text = ""
a.image.SetImage(nil, false)
}
func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer { func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container) return widget.NewSimpleRenderer(a.container)
} }
@@ -327,5 +356,5 @@ func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *mediaprovider.Playlis
} }
func (s *playlistPageState) Restore() Page { func (s *playlistPageState) Restore() Page {
return newPlaylistPage(s.playlistID, s.conf, s.contr, s.sm, s.pm, s.im, s.trackSort) return newPlaylistPage(s.playlistID, s.conf, s.contr, s.widgetPool, s.sm, s.pm, s.im, s.trackSort)
} }
+14 -14
View File
@@ -12,18 +12,18 @@ type NavigationHandler interface {
} }
type Router struct { type Router struct {
App *backend.App App *backend.App
Controller *controller.Controller Controller *controller.Controller
Nav NavigationHandler Nav NavigationHandler
widgetCache util.WidgetCache widgetPool util.WidgetPool
} }
func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router { func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router {
r := Router{ r := Router{
App: app, App: app,
Controller: controller, Controller: controller,
Nav: nav, Nav: nav,
widgetCache: util.NewWidgetCache(), widgetPool: util.NewWidgetPool(),
} }
return r return r
} }
@@ -31,27 +31,27 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
func (r Router) CreatePage(rte controller.Route) Page { func (r Router) CreatePage(rte controller.Route) Page {
switch rte.Page { switch rte.Page {
case controller.Album: case controller.Album:
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, &r.widgetCache, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller) return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, &r.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Albums: case controller.Albums:
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Artist: case controller.Artist:
return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, &r.widgetCache, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller) return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, &r.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Artists: case controller.Artists:
return NewArtistsPage(r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) return NewArtistsPage(r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Favorites: case controller.Favorites:
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager) return NewFavoritesPage(&r.App.Config.FavoritesPage, &r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
case controller.Genre: case controller.Genre:
return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Genres: case controller.Genres:
return NewGenresPage(r.Controller, r.App.ServerManager.Server) return NewGenresPage(r.Controller, r.App.ServerManager.Server)
case controller.NowPlaying: case controller.NowPlaying:
return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, r.App.Player) return NewNowPlayingPage(rte.Arg, r.Controller, &r.widgetPool, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, r.App.Player)
case controller.Playlist: case controller.Playlist:
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, &r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
case controller.Playlists: case controller.Playlists:
return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server) return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server)
case controller.Tracks: case controller.Tracks:
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server) return NewTracksPage(r.Controller, &r.App.Config.TracksPage, &r.widgetPool, r.App.ServerManager.Server)
} }
return nil return nil
} }
+13 -4
View File
@@ -7,6 +7,7 @@ import (
"github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
"github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util"
"github.com/dweymouth/supersonic/ui/widgets" "github.com/dweymouth/supersonic/ui/widgets"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -34,16 +35,22 @@ type TracksPage struct {
type tracksPageState struct { type tracksPageState struct {
searchText string searchText string
widgetPool *util.WidgetPool
contr *controller.Controller contr *controller.Controller
conf *backend.TracksPageConfig conf *backend.TracksPageConfig
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
} }
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, mp mediaprovider.MediaProvider) *TracksPage { func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage {
t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, mp: mp}} t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, widgetPool: pool, mp: mp}}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
t.tracklist = widgets.NewTracklist(nil) if tl := t.widgetPool.Obtain(util.WidgetTypeTracklist); tl != nil {
t.tracklist = tl.(*widgets.Tracklist)
t.tracklist.Reset()
} else {
t.tracklist = widgets.NewTracklist(nil)
}
t.tracklist.Options = widgets.TracklistOptions{ t.tracklist.Options = widgets.TracklistOptions{
DisableSorting: true, DisableSorting: true,
AutoNumber: true, AutoNumber: true,
@@ -143,12 +150,14 @@ func (t *TracksPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (t *TracksPage) Save() SavedPage { func (t *TracksPage) Save() SavedPage {
t.loader.Dispose()
t.widgetPool.Release(util.WidgetTypeTracklist, t.tracklist)
state := t.tracksPageState state := t.tracksPageState
return &state return &state
} }
func (s *tracksPageState) Restore() Page { func (s *tracksPageState) Restore() Page {
t := NewTracksPage(s.contr, s.conf, s.mp) t := NewTracksPage(s.contr, s.conf, s.widgetPool, s.mp)
t.searchText = s.searchText t.searchText = s.searchText
if t.searchText != "" { if t.searchText != "" {
t.searcher.Entry.Text = t.searchText t.searcher.Entry.Text = t.searchText
-50
View File
@@ -1,50 +0,0 @@
package util
import (
"time"
"fyne.io/fyne/v2"
)
type WidgetType string
const (
WidgetTypeAlbumPageHeader WidgetType = "AlbumPageHeader"
WidgetTypeArtistPageHeader WidgetType = "ArtistPageHeader"
)
type WidgetCache struct {
cache map[WidgetType][]cachedWidget
}
type cachedWidget struct {
widget fyne.CanvasObject
releasedAt int64 // unixMillis
}
func NewWidgetCache() WidgetCache {
return WidgetCache{
cache: make(map[WidgetType][]cachedWidget),
}
}
func (w *WidgetCache) Obtain(typ WidgetType) fyne.CanvasObject {
var widget fyne.CanvasObject
if ws, ok := w.cache[typ]; ok && len(ws) > 0 {
i := len(ws) - 1
widget = ws[i].widget
ws[i].widget = nil
w.cache[typ] = ws[:i]
}
return widget
}
func (w *WidgetCache) Release(typ WidgetType, wid fyne.CanvasObject) {
if _, ok := w.cache[typ]; !ok {
w.cache[typ] = make([]cachedWidget, 0)
}
w.cache[typ] = append(w.cache[typ], cachedWidget{
widget: wid,
releasedAt: time.Now().UnixMilli(),
})
}
+60
View File
@@ -0,0 +1,60 @@
package util
import (
"time"
"fyne.io/fyne/v2"
)
type WidgetType int
const (
WidgetTypeAlbumPageHeader WidgetType = iota
WidgetTypeArtistPageHeader
WidgetTypePlaylistPageHeader
WidgetTypeTracklist
)
// A pool to share commonly-used widgets across pages to reduce
// creation of new widgets and memory allocations.
// It is not thread-safe, which is fine for its current use.
type WidgetPool struct {
cache map[WidgetType][]cachedWidget
}
type cachedWidget struct {
widget fyne.CanvasObject
releasedAt int64 // unixMillis
}
func NewWidgetPool() WidgetPool {
return WidgetPool{
cache: make(map[WidgetType][]cachedWidget),
}
}
// Obtain obtains a widget of the given type from the pool, if one exists.
// Returns nil if there is no available widget.
func (w *WidgetPool) Obtain(typ WidgetType) fyne.CanvasObject {
var widget fyne.CanvasObject
if ws, ok := w.cache[typ]; ok && len(ws) > 0 {
i := len(ws) - 1
widget = ws[i].widget
ws[i].widget = nil
w.cache[typ] = ws[:i]
}
return widget
}
// Release releases a widget into the pool.
// The widget must not be modified by the releaser after release,
// since it may be Obtained for a new use at any time.
func (w *WidgetPool) Release(typ WidgetType, wid fyne.CanvasObject) {
if _, ok := w.cache[typ]; !ok {
w.cache[typ] = make([]cachedWidget, 0)
}
w.cache[typ] = append(w.cache[typ], cachedWidget{
widget: wid,
releasedAt: time.Now().UnixMilli(),
})
}
+15 -1
View File
@@ -7,6 +7,8 @@ import (
// Component that manages lazily loading more tracks into a Tracklist // Component that manages lazily loading more tracks into a Tracklist
// as the user scrolls near the bottom. // as the user scrolls near the bottom.
type TracklistLoader struct { type TracklistLoader struct {
disposed bool
tracklist *Tracklist tracklist *Tracklist
iter mediaprovider.TrackIterator iter mediaprovider.TrackIterator
@@ -28,11 +30,17 @@ func NewTracklistLoader(tracklist *Tracklist, iter mediaprovider.TrackIterator)
return t return t
} }
// Cancels all asynchronous loads so that they will no longer modify the tracklist.
func (t *TracklistLoader) Dispose() {
t.disposed = true
t.tracklist.OnTrackShown = nil
}
func (t *TracklistLoader) onTrackShown(tracknum int) { func (t *TracklistLoader) onTrackShown(tracknum int) {
if tracknum > t.highestShown { if tracknum > t.highestShown {
t.highestShown = tracknum t.highestShown = tracknum
} }
if t.highestShown >= t.len-25 && !t.fetching && !t.done { if t.highestShown >= t.len-25 && !t.fetching && !t.done && !t.disposed {
t.fetching = true t.fetching = true
go t.loadMoreTracks(25) go t.loadMoreTracks(25)
} }
@@ -52,6 +60,12 @@ func (t *TracklistLoader) loadMoreTracks(num int) {
break break
} }
t.trackBuffer = append(t.trackBuffer, tr) t.trackBuffer = append(t.trackBuffer, tr)
if t.disposed {
break
}
}
if t.disposed {
return
} }
t.tracklist.AppendTracks(t.trackBuffer) t.tracklist.AppendTracks(t.trackBuffer)
t.tracklist.Refresh() t.tracklist.Refresh()