refactor: move ImageLoader to ui/util

This commit is contained in:
Drew Weymouth
2023-11-05 14:30:57 -08:00
parent 5fd3f96129
commit 7d7d315d74
5 changed files with 78 additions and 69 deletions
-49
View File
@@ -188,55 +188,6 @@ func (i *ImageManager) RefreshCachedArtistImageIfExpired(artistID string, imgURL
return err 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 { func (i *ImageManager) ensureCoverCacheDir() string {
// if user logged out with pending fetches in progress, // if user logged out with pending fetches in progress,
// make sure we don't write to nil (00000000-*0) cache directory // make sure we don't write to nil (00000000-*0) cache directory
+8 -8
View File
@@ -11,10 +11,10 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util"
"github.com/dweymouth/supersonic/ui/widgets" "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 SearchEntry fyne.Focusable // exported so it can be focused by the Controller
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
im *backend.ImageManager imgSource util.ImageFetcher
resultsMutex sync.RWMutex resultsMutex sync.RWMutex
searchResults []*mediaprovider.SearchResult searchResults []*mediaprovider.SearchResult
@@ -37,10 +37,10 @@ type QuickSearch struct {
content *fyne.Container content *fyne.Container
} }
func NewQuickSearch(mp mediaprovider.MediaProvider, im *backend.ImageManager) *QuickSearch { func NewQuickSearch(mp mediaprovider.MediaProvider, im util.ImageFetcher) *QuickSearch {
q := &QuickSearch{ q := &QuickSearch{
mp: mp, mp: mp,
im: im, imgSource: im,
} }
q.ExtendBaseWidget(q) q.ExtendBaseWidget(q)
@@ -157,7 +157,7 @@ type quickSearchResult struct {
index int index int
contentType mediaprovider.ContentType contentType mediaprovider.ContentType
imageLoader backend.ThumbnailLoader imageLoader util.ThumbnailLoader
image *widgets.ImagePlaceholder image *widgets.ImagePlaceholder
title *widget.Label title *widget.Label
@@ -176,7 +176,7 @@ func newQuickSearchResult(parent *QuickSearch) *quickSearchResult {
qs.title.Wrapping = fyne.TextTruncate qs.title.Wrapping = fyne.TextTruncate
qs.secondary.Wrapping = fyne.TextTruncate qs.secondary.Wrapping = fyne.TextTruncate
qs.ExtendBaseWidget(qs) 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.image.SetImage(im, false)
}) })
qs.imageLoader.OnBeforeLoad = func() { qs.imageLoader.OnBeforeLoad = func() {
+63
View File
@@ -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)
}
}
+5 -10
View File
@@ -2,12 +2,11 @@ package widgets
import ( import (
"context" "context"
"image"
"sync" "sync"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/util"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -37,10 +36,6 @@ func (b *BatchingIterator) NextN(n int) []*mediaprovider.Album {
return results return results
} }
type ThumbnailLoaderFactory interface {
NewThumbnailLoader(func(image.Image)) backend.ThumbnailLoader
}
type GridViewIterator interface { type GridViewIterator interface {
NextN(int) []GridViewItemModel NextN(int) []GridViewItemModel
} }
@@ -81,7 +76,7 @@ type GridView struct {
type GridViewState struct { type GridViewState struct {
items []GridViewItemModel items []GridViewItemModel
iter GridViewIterator iter GridViewIterator
imageFetcher ThumbnailLoaderFactory imageFetcher util.ImageFetcher
Placeholder fyne.Resource Placeholder fyne.Resource
highestShown int highestShown int
done bool done bool
@@ -98,7 +93,7 @@ type GridViewState struct {
var _ fyne.Widget = (*GridView)(nil) 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{ g := &GridView{
GridViewState: GridViewState{ GridViewState: GridViewState{
items: items, items: items,
@@ -112,7 +107,7 @@ func NewFixedGridView(items []GridViewItemModel, fetch ThumbnailLoaderFactory, p
return g 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{ g := &GridView{
GridViewState: GridViewState{ GridViewState: GridViewState{
iter: iter, iter: iter,
@@ -202,7 +197,7 @@ func (g *GridView) createGridWrap() {
// create func // create func
func() fyne.CanvasObject { func() fyne.CanvasObject {
card := NewGridViewItem(g.Placeholder) 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.ImgLoader.OnBeforeLoad = func() { card.Cover.SetImage(nil) }
card.OnPlay = func() { g.onPlay(card.ItemID(), false) } card.OnPlay = func() { g.onPlay(card.ItemID(), false) }
card.OnShowSecondaryPage = func(id string) { card.OnShowSecondaryPage = func(id string) {
+2 -2
View File
@@ -3,10 +3,10 @@ package widgets
import ( import (
"image" "image"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
"github.com/dweymouth/supersonic/ui/util"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
@@ -141,7 +141,7 @@ type GridViewItem struct {
// updated by GridView // updated by GridView
Cover *coverImage Cover *coverImage
ImgLoader backend.ThumbnailLoader ImgLoader util.ThumbnailLoader
OnPlay func() OnPlay func()
OnShowContextMenu func(fyne.Position) OnShowContextMenu func(fyne.Position)