diff --git a/backend/imagemanager.go b/backend/imagemanager.go index aa7bf6b..96da5bf 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -188,55 +188,6 @@ func (i *ImageManager) RefreshCachedArtistImageIfExpired(artistID string, imgURL return err } -// ThumbnailLoader is a utility type that exposes a single API to load -// a cover thumbnail by ID. If the image is immediately available in -// the cache, OnLoaded will be called immediately. If it is not, -// OnBeforeLoad will be called first, then OnLoaded will be called async -// once the image is available. -// Any subsequent calls to Load will cancel the previous load if not yet completed. -type ThumbnailLoader struct { - prevLoadCancel context.CancelFunc - im *ImageManager - - OnBeforeLoad func() - OnLoaded func(image.Image) -} - -func (i *ImageManager) NewThumbnailLoader(onLoaded func(image.Image)) ThumbnailLoader { - return ThumbnailLoader{im: i, OnLoaded: onLoaded} -} - -func (i *ThumbnailLoader) Load(coverID string) { - if i.prevLoadCancel != nil { - i.prevLoadCancel() - } - if coverID == "" { - i.callOnLoaded(nil) - return - } - if img, ok := i.im.GetCoverThumbnailFromCache(coverID); ok { - i.callOnLoaded(img) - return - } - if i.OnBeforeLoad != nil { - i.OnBeforeLoad() - } - i.prevLoadCancel = i.im.GetCoverThumbnailAsync(coverID, func(img image.Image, err error) { - if err != nil { - log.Printf("Error loading cover image: %s", err.Error()) - } else { - i.callOnLoaded(img) - } - i.prevLoadCancel() // Done. Release resources associated with un-cancelled ctx - }) -} - -func (i *ThumbnailLoader) callOnLoaded(im image.Image) { - if i.OnLoaded != nil { - i.OnLoaded(im) - } -} - func (i *ImageManager) ensureCoverCacheDir() string { // if user logged out with pending fetches in progress, // make sure we don't write to nil (00000000-*0) cache directory diff --git a/ui/dialogs/quicksearch.go b/ui/dialogs/quicksearch.go index 2698aaf..52ba02d 100644 --- a/ui/dialogs/quicksearch.go +++ b/ui/dialogs/quicksearch.go @@ -11,10 +11,10 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/ui/layouts" myTheme "github.com/dweymouth/supersonic/ui/theme" + "github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/widgets" ) @@ -26,8 +26,8 @@ type QuickSearch struct { SearchEntry fyne.Focusable // exported so it can be focused by the Controller - mp mediaprovider.MediaProvider - im *backend.ImageManager + mp mediaprovider.MediaProvider + imgSource util.ImageFetcher resultsMutex sync.RWMutex searchResults []*mediaprovider.SearchResult @@ -37,10 +37,10 @@ type QuickSearch struct { content *fyne.Container } -func NewQuickSearch(mp mediaprovider.MediaProvider, im *backend.ImageManager) *QuickSearch { +func NewQuickSearch(mp mediaprovider.MediaProvider, im util.ImageFetcher) *QuickSearch { q := &QuickSearch{ - mp: mp, - im: im, + mp: mp, + imgSource: im, } q.ExtendBaseWidget(q) @@ -157,7 +157,7 @@ type quickSearchResult struct { index int contentType mediaprovider.ContentType - imageLoader backend.ThumbnailLoader + imageLoader util.ThumbnailLoader image *widgets.ImagePlaceholder title *widget.Label @@ -176,7 +176,7 @@ func newQuickSearchResult(parent *QuickSearch) *quickSearchResult { qs.title.Wrapping = fyne.TextTruncate qs.secondary.Wrapping = fyne.TextTruncate qs.ExtendBaseWidget(qs) - qs.imageLoader = parent.im.NewThumbnailLoader(func(im image.Image) { + qs.imageLoader = util.NewThumbnailLoader(parent.imgSource, func(im image.Image) { qs.image.SetImage(im, false) }) qs.imageLoader.OnBeforeLoad = func() { diff --git a/ui/util/thumbnailloader.go b/ui/util/thumbnailloader.go new file mode 100644 index 0000000..f1fc063 --- /dev/null +++ b/ui/util/thumbnailloader.go @@ -0,0 +1,63 @@ +package util + +import ( + "context" + "image" + "log" +) + +// ThumbnailLoader is a utility type that exposes a single API to load +// a cover thumbnail by ID. If the image is immediately available in +// the cache, OnLoaded will be called immediately. If it is not, +// OnBeforeLoad will be called first, then OnLoaded will be called async +// once the image is available. +// Any subsequent calls to Load will cancel the previous load if not yet completed. +type ThumbnailLoader struct { + prevLoadCancel context.CancelFunc + im ImageFetcher + + OnBeforeLoad func() + OnLoaded func(image.Image) +} + +// Image backend interface for the ThumbnailLoader +// impl: backend.ImageManager +type ImageFetcher interface { + GetCoverThumbnailFromCache(string) (image.Image, bool) + GetCoverThumbnailAsync(string, func(image.Image, error)) context.CancelFunc +} + +func NewThumbnailLoader(im ImageFetcher, onLoaded func(image.Image)) ThumbnailLoader { + return ThumbnailLoader{im: im, OnLoaded: onLoaded} +} + +func (i *ThumbnailLoader) Load(coverID string) { + if i.prevLoadCancel != nil { + i.prevLoadCancel() + } + if coverID == "" { + i.callOnLoaded(nil) + return + } + if img, ok := i.im.GetCoverThumbnailFromCache(coverID); ok { + i.callOnLoaded(img) + return + } + if i.OnBeforeLoad != nil { + i.OnBeforeLoad() + } + i.prevLoadCancel = i.im.GetCoverThumbnailAsync(coverID, func(img image.Image, err error) { + if err != nil { + log.Printf("Error loading cover image: %s", err.Error()) + } else { + i.callOnLoaded(img) + } + i.prevLoadCancel() // Done. Release resources associated with un-cancelled ctx + }) +} + +func (i *ThumbnailLoader) callOnLoaded(im image.Image) { + if i.OnLoaded != nil { + i.OnLoaded(im) + } +} diff --git a/ui/widgets/gridview.go b/ui/widgets/gridview.go index 95da758..01f1f6f 100644 --- a/ui/widgets/gridview.go +++ b/ui/widgets/gridview.go @@ -2,12 +2,11 @@ package widgets import ( "context" - "image" "sync" - "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" + "github.com/dweymouth/supersonic/ui/util" "fyne.io/fyne/v2" "fyne.io/fyne/v2/widget" @@ -37,10 +36,6 @@ func (b *BatchingIterator) NextN(n int) []*mediaprovider.Album { return results } -type ThumbnailLoaderFactory interface { - NewThumbnailLoader(func(image.Image)) backend.ThumbnailLoader -} - type GridViewIterator interface { NextN(int) []GridViewItemModel } @@ -81,7 +76,7 @@ type GridView struct { type GridViewState struct { items []GridViewItemModel iter GridViewIterator - imageFetcher ThumbnailLoaderFactory + imageFetcher util.ImageFetcher Placeholder fyne.Resource highestShown int done bool @@ -98,7 +93,7 @@ type GridViewState struct { var _ fyne.Widget = (*GridView)(nil) -func NewFixedGridView(items []GridViewItemModel, fetch ThumbnailLoaderFactory, placeholder fyne.Resource) *GridView { +func NewFixedGridView(items []GridViewItemModel, fetch util.ImageFetcher, placeholder fyne.Resource) *GridView { g := &GridView{ GridViewState: GridViewState{ items: items, @@ -112,7 +107,7 @@ func NewFixedGridView(items []GridViewItemModel, fetch ThumbnailLoaderFactory, p return g } -func NewGridView(iter GridViewIterator, fetch ThumbnailLoaderFactory, placeholder fyne.Resource) *GridView { +func NewGridView(iter GridViewIterator, fetch util.ImageFetcher, placeholder fyne.Resource) *GridView { g := &GridView{ GridViewState: GridViewState{ iter: iter, @@ -202,7 +197,7 @@ func (g *GridView) createGridWrap() { // create func func() fyne.CanvasObject { card := NewGridViewItem(g.Placeholder) - card.ImgLoader = g.imageFetcher.NewThumbnailLoader(card.Cover.SetImage) + card.ImgLoader = util.NewThumbnailLoader(g.imageFetcher, card.Cover.SetImage) card.ImgLoader.OnBeforeLoad = func() { card.Cover.SetImage(nil) } card.OnPlay = func() { g.onPlay(card.ItemID(), false) } card.OnShowSecondaryPage = func(id string) { diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index 5581b48..d928550 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -3,10 +3,10 @@ package widgets import ( "image" - "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/layouts" + "github.com/dweymouth/supersonic/ui/util" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -141,7 +141,7 @@ type GridViewItem struct { // updated by GridView Cover *coverImage - ImgLoader backend.ThumbnailLoader + ImgLoader util.ThumbnailLoader OnPlay func() OnShowContextMenu func(fyne.Position)