refactor: move ImageLoader to ui/util
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user