refactor 1: renaming
This commit is contained in:
+1
-1
@@ -65,7 +65,7 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A
|
|||||||
a.LibraryManager = NewLibraryManager(a.ServerManager)
|
a.LibraryManager = NewLibraryManager(a.ServerManager)
|
||||||
a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName))
|
a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName))
|
||||||
a.LibraryManager.PreCacheCoverFn = func(coverID string) {
|
a.LibraryManager.PreCacheCoverFn = func(coverID string) {
|
||||||
_, _ = a.ImageManager.GetAlbumThumbnail(coverID)
|
_, _ = a.ImageManager.GetCoverThumbnail(coverID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func NewImageManager(ctx context.Context, s *ServerManager, baseCacheDir string)
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImageManager) GetAlbumThumbnailFromCache(coverID string) (image.Image, bool) {
|
func (i *ImageManager) GetCoverThumbnailFromCache(coverID string) (image.Image, bool) {
|
||||||
img, err := i.thumbnailCache.GetExtendTTL(coverID, i.thumbnailCache.DefaultTTL)
|
img, err := i.thumbnailCache.GetExtendTTL(coverID, i.thumbnailCache.DefaultTTL)
|
||||||
if err == nil && img != nil {
|
if err == nil && img != nil {
|
||||||
return img, true
|
return img, true
|
||||||
@@ -53,14 +53,14 @@ func (i *ImageManager) GetAlbumThumbnailFromCache(coverID string) (image.Image,
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImageManager) GetAlbumThumbnail(coverID string) (image.Image, error) {
|
func (i *ImageManager) GetCoverThumbnail(coverID string) (image.Image, error) {
|
||||||
if im, ok := i.GetAlbumThumbnailFromCache(coverID); ok {
|
if im, ok := i.GetCoverThumbnailFromCache(coverID); ok {
|
||||||
return im, nil
|
return im, nil
|
||||||
}
|
}
|
||||||
return i.fetchAndCacheCoverFromDiskOrServer(coverID, i.thumbnailCache.DefaultTTL)
|
return i.fetchAndCacheCoverFromDiskOrServer(coverID, i.thumbnailCache.DefaultTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImageManager) GetAlbumThumbnailWithTTL(coverID string, ttl time.Duration) (image.Image, error) {
|
func (i *ImageManager) GetCoverThumbnailWithTTL(coverID string, ttl time.Duration) (image.Image, error) {
|
||||||
// in-memory cache
|
// in-memory cache
|
||||||
if img, err := i.thumbnailCache.GetWithNewTTL(coverID, ttl); err == nil {
|
if img, err := i.thumbnailCache.GetWithNewTTL(coverID, ttl); err == nil {
|
||||||
return img, nil
|
return img, nil
|
||||||
@@ -68,7 +68,7 @@ func (i *ImageManager) GetAlbumThumbnailWithTTL(coverID string, ttl time.Duratio
|
|||||||
return i.fetchAndCacheCoverFromDiskOrServer(coverID, ttl)
|
return i.fetchAndCacheCoverFromDiskOrServer(coverID, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImageManager) GetFullSizeAlbumCover(coverID string) (image.Image, error) {
|
func (i *ImageManager) GetFullSizeCoverArt(coverID string) (image.Image, error) {
|
||||||
if i.cachedFullSizeCoverID == coverID {
|
if i.cachedFullSizeCoverID == coverID {
|
||||||
return i.cachedFullSizeCover, nil
|
return i.cachedFullSizeCover, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ func (bp *BottomPanel) onSongChange(song *subsonic.Child, _ *subsonic.Child) {
|
|||||||
// be in cache for the next song if it's from the same album, or
|
// be in cache for the next song if it's from the same album, or
|
||||||
// if the user navigates to the album page for the track
|
// if the user navigates to the album page for the track
|
||||||
imgTTLSec := song.Duration + 30
|
imgTTLSec := song.Duration + 30
|
||||||
im, _ = bp.ImageManager.GetAlbumThumbnailWithTTL(song.CoverArt, time.Duration(imgTTLSec)*time.Second)
|
im, _ = bp.ImageManager.GetCoverThumbnailWithTTL(song.CoverArt, time.Duration(imgTTLSec)*time.Second)
|
||||||
}
|
}
|
||||||
bp.NowPlaying.Update(song.Title, song.Artist, song.Album, im)
|
bp.NowPlaying.Update(song.Title, song.Artist, song.Album, im)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana
|
|||||||
a.Refresh()
|
a.Refresh()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if cover, err := im.GetAlbumThumbnail(album.CoverArt); err == nil {
|
if cover, err := im.GetCoverThumbnail(album.CoverArt); err == nil {
|
||||||
a.cover.Image.Image = cover
|
a.cover.Image.Image = cover
|
||||||
a.cover.Refresh()
|
a.cover.Refresh()
|
||||||
} else {
|
} else {
|
||||||
@@ -244,7 +244,7 @@ func (a *AlbumPageHeader) toggleFavorited() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumPageHeader) showPopUpCover() {
|
func (a *AlbumPageHeader) showPopUpCover() {
|
||||||
cover, err := a.page.im.GetFullSizeAlbumCover(a.coverID)
|
cover, err := a.page.im.GetFullSizeCoverArt(a.coverID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error getting full size album cover: %s", err.Error())
|
log.Printf("error getting full size album cover: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
+12
-12
@@ -24,8 +24,8 @@ type AlbumsPage struct {
|
|||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
grid *widgets.AlbumGrid
|
grid *widgets.GridView
|
||||||
searchGrid *widgets.AlbumGrid
|
searchGrid *widgets.GridView
|
||||||
searcher *widgets.Searcher
|
searcher *widgets.Searcher
|
||||||
searchText string
|
searchText string
|
||||||
titleDisp *widget.RichText
|
titleDisp *widget.RichText
|
||||||
@@ -73,9 +73,9 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
|
|||||||
a.sortOrder.Selected = cfg.SortOrder
|
a.sortOrder.Selected = cfg.SortOrder
|
||||||
iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected))
|
iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected))
|
||||||
a.grid = widgets.NewAlbumGrid(iter, im, false /*showYear*/)
|
a.grid = widgets.NewAlbumGrid(iter, im, false /*showYear*/)
|
||||||
a.grid.OnPlayAlbum = a.onPlayAlbum
|
a.grid.OnPlay = a.onPlayAlbum
|
||||||
a.grid.OnShowArtistPage = a.onShowArtistPage
|
a.grid.OnShowSecondaryPage = a.onShowArtistPage
|
||||||
a.grid.OnShowAlbumPage = a.onShowAlbumPage
|
a.grid.OnShowItemPage = a.onShowAlbumPage
|
||||||
a.searcher = widgets.NewSearcher()
|
a.searcher = widgets.NewSearcher()
|
||||||
a.searcher.OnSearched = a.OnSearched
|
a.searcher.OnSearched = a.OnSearched
|
||||||
a.createContainer(false)
|
a.createContainer(false)
|
||||||
@@ -116,13 +116,13 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
|
|||||||
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
|
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
|
||||||
a.sortOrder.Selected = saved.sortOrder
|
a.sortOrder.Selected = saved.sortOrder
|
||||||
a.sortOrder.OnChanged = a.onSortOrderChanged
|
a.sortOrder.OnChanged = a.onSortOrderChanged
|
||||||
a.grid = widgets.NewAlbumGridFromState(saved.gridState)
|
a.grid = widgets.NewGridViewFromState(saved.gridState)
|
||||||
a.searcher = widgets.NewSearcher()
|
a.searcher = widgets.NewSearcher()
|
||||||
a.searcher.OnSearched = a.OnSearched
|
a.searcher.OnSearched = a.OnSearched
|
||||||
a.searcher.Entry.Text = saved.searchText
|
a.searcher.Entry.Text = saved.searchText
|
||||||
a.searchText = saved.searchText
|
a.searchText = saved.searchText
|
||||||
if a.searchText != "" {
|
if a.searchText != "" {
|
||||||
a.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState)
|
a.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
||||||
}
|
}
|
||||||
a.createContainer(saved.searchText != "")
|
a.createContainer(saved.searchText != "")
|
||||||
|
|
||||||
@@ -181,9 +181,9 @@ func (a *AlbumsPage) Save() SavedPage {
|
|||||||
func (a *AlbumsPage) doSearch(query string) {
|
func (a *AlbumsPage) doSearch(query string) {
|
||||||
if a.searchGrid == nil {
|
if a.searchGrid == nil {
|
||||||
a.searchGrid = widgets.NewAlbumGrid(a.lm.SearchIter(query), a.im, false /*showYear*/)
|
a.searchGrid = widgets.NewAlbumGrid(a.lm.SearchIter(query), a.im, false /*showYear*/)
|
||||||
a.searchGrid.OnPlayAlbum = a.onPlayAlbum
|
a.searchGrid.OnPlay = a.onPlayAlbum
|
||||||
a.searchGrid.OnShowAlbumPage = a.onShowAlbumPage
|
a.searchGrid.OnShowItemPage = a.onShowAlbumPage
|
||||||
a.searchGrid.OnShowArtistPage = a.onShowArtistPage
|
a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage
|
||||||
} else {
|
} else {
|
||||||
a.searchGrid.Reset(a.lm.SearchIter(query))
|
a.searchGrid.Reset(a.lm.SearchIter(query))
|
||||||
}
|
}
|
||||||
@@ -225,8 +225,8 @@ type savedAlbumsPage struct {
|
|||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
sortOrder string
|
sortOrder string
|
||||||
gridState widgets.AlbumGridState
|
gridState widgets.GridViewState
|
||||||
searchGridState widgets.AlbumGridState
|
searchGridState widgets.GridViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedAlbumsPage) Restore() Page {
|
func (s *savedAlbumsPage) Restore() Page {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type ArtistPage struct {
|
|||||||
|
|
||||||
artistInfo *subsonic.ArtistID3
|
artistInfo *subsonic.ArtistID3
|
||||||
|
|
||||||
albumGrid *widgets.AlbumGrid
|
albumGrid *widgets.GridView
|
||||||
tracklistCtr *fyne.Container
|
tracklistCtr *fyne.Container
|
||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
header *ArtistPageHeader
|
header *ArtistPageHeader
|
||||||
@@ -173,8 +173,8 @@ func (a *ArtistPage) showAlbumGrid() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.albumGrid = widgets.NewFixedAlbumGrid(a.artistInfo.Album, a.im, true /*showYear*/)
|
a.albumGrid = widgets.NewFixedAlbumGrid(a.artistInfo.Album, a.im, true /*showYear*/)
|
||||||
a.albumGrid.OnPlayAlbum = a.onPlayAlbum
|
a.albumGrid.OnPlay = a.onPlayAlbum
|
||||||
a.albumGrid.OnShowAlbumPage = a.onShowAlbumPage
|
a.albumGrid.OnShowItemPage = a.onShowAlbumPage
|
||||||
}
|
}
|
||||||
a.container.Objects[0].(*fyne.Container).Objects[0] = a.albumGrid
|
a.container.Objects[0].(*fyne.Container).Objects[0] = a.albumGrid
|
||||||
a.container.Objects[0].Refresh()
|
a.container.Objects[0].Refresh()
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ type FavoritesPage struct {
|
|||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
pendingViewSwitch bool
|
pendingViewSwitch bool
|
||||||
|
|
||||||
grid *widgets.AlbumGrid
|
grid *widgets.GridView
|
||||||
searchGrid *widgets.AlbumGrid
|
searchGrid *widgets.GridView
|
||||||
artistListCtr *fyne.Container
|
artistListCtr *fyne.Container
|
||||||
tracklistCtr *fyne.Container
|
tracklistCtr *fyne.Container
|
||||||
searcher *widgets.Searcher
|
searcher *widgets.Searcher
|
||||||
@@ -82,9 +82,9 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) connectGridActions() {
|
func (a *FavoritesPage) connectGridActions() {
|
||||||
a.grid.OnPlayAlbum = a.onPlayAlbum
|
a.grid.OnPlay = a.onPlayAlbum
|
||||||
a.grid.OnShowAlbumPage = a.onShowAlbumPage
|
a.grid.OnShowItemPage = a.onShowAlbumPage
|
||||||
a.grid.OnShowArtistPage = a.onShowArtistPage
|
a.grid.OnShowSecondaryPage = a.onShowArtistPage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) {
|
func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) {
|
||||||
@@ -105,11 +105,11 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
|
|||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.createHeader(saved.activeToggleBtn, saved.searchText)
|
a.createHeader(saved.activeToggleBtn, saved.searchText)
|
||||||
a.grid = widgets.NewAlbumGridFromState(saved.gridState)
|
a.grid = widgets.NewGridViewFromState(saved.gridState)
|
||||||
a.connectGridActions()
|
a.connectGridActions()
|
||||||
|
|
||||||
if saved.searchText != "" {
|
if saved.searchText != "" {
|
||||||
a.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState)
|
a.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if saved.activeToggleBtn == 1 {
|
if saved.activeToggleBtn == 1 {
|
||||||
@@ -221,9 +221,9 @@ func (a *FavoritesPage) doSearchAlbums(query string) {
|
|||||||
})
|
})
|
||||||
if a.searchGrid == nil {
|
if a.searchGrid == nil {
|
||||||
a.searchGrid = widgets.NewAlbumGrid(iter, a.im, false /*showYear*/)
|
a.searchGrid = widgets.NewAlbumGrid(iter, a.im, false /*showYear*/)
|
||||||
a.searchGrid.OnPlayAlbum = a.onPlayAlbum
|
a.searchGrid.OnPlay = a.onPlayAlbum
|
||||||
a.searchGrid.OnShowAlbumPage = a.onShowAlbumPage
|
a.searchGrid.OnShowItemPage = a.onShowAlbumPage
|
||||||
a.searchGrid.OnShowArtistPage = a.onShowArtistPage
|
a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage
|
||||||
} else {
|
} else {
|
||||||
a.searchGrid.Reset(iter)
|
a.searchGrid.Reset(iter)
|
||||||
}
|
}
|
||||||
@@ -352,8 +352,8 @@ type savedFavoritesPage struct {
|
|||||||
sm *backend.ServerManager
|
sm *backend.ServerManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
gridState widgets.AlbumGridState
|
gridState widgets.GridViewState
|
||||||
searchGridState widgets.AlbumGridState
|
searchGridState widgets.GridViewState
|
||||||
searchText string
|
searchText string
|
||||||
activeToggleBtn int
|
activeToggleBtn int
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -24,8 +24,8 @@ type GenrePage struct {
|
|||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
grid *widgets.AlbumGrid
|
grid *widgets.GridView
|
||||||
searchGrid *widgets.AlbumGrid
|
searchGrid *widgets.GridView
|
||||||
searcher *widgets.Searcher
|
searcher *widgets.Searcher
|
||||||
searchText string
|
searchText string
|
||||||
titleDisp *widget.RichText
|
titleDisp *widget.RichText
|
||||||
@@ -53,9 +53,9 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
|
|||||||
g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs)
|
g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs)
|
||||||
iter := g.lm.GenreIter(g.genre)
|
iter := g.lm.GenreIter(g.genre)
|
||||||
g.grid = widgets.NewAlbumGrid(iter, g.im, false)
|
g.grid = widgets.NewAlbumGrid(iter, g.im, false)
|
||||||
g.grid.OnPlayAlbum = g.onPlayAlbum
|
g.grid.OnPlay = g.onPlayAlbum
|
||||||
g.grid.OnShowArtistPage = g.onShowArtistPage
|
g.grid.OnShowSecondaryPage = g.onShowArtistPage
|
||||||
g.grid.OnShowAlbumPage = g.onShowAlbumPage
|
g.grid.OnShowItemPage = g.onShowAlbumPage
|
||||||
g.searcher = widgets.NewSearcher()
|
g.searcher = widgets.NewSearcher()
|
||||||
g.searcher.OnSearched = g.OnSearched
|
g.searcher.OnSearched = g.OnSearched
|
||||||
g.createContainer(false)
|
g.createContainer(false)
|
||||||
@@ -94,13 +94,13 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage {
|
|||||||
SizeName: theme.SizeNameHeadingText,
|
SizeName: theme.SizeNameHeadingText,
|
||||||
}
|
}
|
||||||
g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs)
|
g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs)
|
||||||
g.grid = widgets.NewAlbumGridFromState(saved.gridState)
|
g.grid = widgets.NewGridViewFromState(saved.gridState)
|
||||||
g.searcher = widgets.NewSearcher()
|
g.searcher = widgets.NewSearcher()
|
||||||
g.searcher.OnSearched = g.OnSearched
|
g.searcher.OnSearched = g.OnSearched
|
||||||
g.searcher.Entry.Text = saved.searchText
|
g.searcher.Entry.Text = saved.searchText
|
||||||
g.searchText = saved.searchText
|
g.searchText = saved.searchText
|
||||||
if g.searchText != "" {
|
if g.searchText != "" {
|
||||||
g.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState)
|
g.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
||||||
}
|
}
|
||||||
g.createContainer(saved.searchText != "")
|
g.createContainer(saved.searchText != "")
|
||||||
|
|
||||||
@@ -177,9 +177,9 @@ func (g *GenrePage) doSearch(query string) {
|
|||||||
})
|
})
|
||||||
if g.searchGrid == nil {
|
if g.searchGrid == nil {
|
||||||
g.searchGrid = widgets.NewAlbumGrid(iter, g.im, false /*showYear*/)
|
g.searchGrid = widgets.NewAlbumGrid(iter, g.im, false /*showYear*/)
|
||||||
g.searchGrid.OnPlayAlbum = g.onPlayAlbum
|
g.searchGrid.OnPlay = g.onPlayAlbum
|
||||||
g.searchGrid.OnShowAlbumPage = g.onShowAlbumPage
|
g.searchGrid.OnShowItemPage = g.onShowAlbumPage
|
||||||
g.searchGrid.OnShowArtistPage = g.onShowArtistPage
|
g.searchGrid.OnShowSecondaryPage = g.onShowArtistPage
|
||||||
} else {
|
} else {
|
||||||
g.searchGrid.Reset(iter)
|
g.searchGrid.Reset(iter)
|
||||||
}
|
}
|
||||||
@@ -198,8 +198,8 @@ type savedGenrePage struct {
|
|||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
gridState widgets.AlbumGridState
|
gridState widgets.GridViewState
|
||||||
searchGridState widgets.AlbumGridState
|
searchGridState widgets.GridViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedGenrePage) Restore() Page {
|
func (s *savedGenrePage) Restore() Page {
|
||||||
|
|||||||
@@ -233,13 +233,13 @@ func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) {
|
|||||||
|
|
||||||
var haveCover bool
|
var haveCover bool
|
||||||
if playlist.CoverArt != "" {
|
if playlist.CoverArt != "" {
|
||||||
if im, err := a.page.im.GetAlbumThumbnail(playlist.CoverArt); err == nil && im != nil {
|
if im, err := a.page.im.GetCoverThumbnail(playlist.CoverArt); err == nil && im != nil {
|
||||||
a.image.SetImage(im, false /*tappable*/)
|
a.image.SetImage(im, false /*tappable*/)
|
||||||
haveCover = true
|
haveCover = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !haveCover {
|
if !haveCover {
|
||||||
if im, err := a.page.im.GetAlbumThumbnail(playlist.ID); err == nil && im != nil {
|
if im, err := a.page.im.GetCoverThumbnail(playlist.ID); err == nil && im != nil {
|
||||||
a.image.SetImage(im, false)
|
a.image.SetImage(im, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
package widgets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"image"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"supersonic/res"
|
|
||||||
"supersonic/ui/layouts"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/driver/desktop"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
|
|
||||||
"github.com/dweymouth/go-subsonic/subsonic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ fyne.Widget = (*AlbumCard)(nil)
|
|
||||||
|
|
||||||
var _ fyne.Widget = (*albumCover)(nil)
|
|
||||||
var _ fyne.Tappable = (*albumCover)(nil)
|
|
||||||
|
|
||||||
type albumCover struct {
|
|
||||||
widget.BaseWidget
|
|
||||||
|
|
||||||
Im *canvas.Image
|
|
||||||
playbtn *canvas.Image
|
|
||||||
OnDoubleTapped func()
|
|
||||||
OnTapped func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAlbumCover() *albumCover {
|
|
||||||
a := &albumCover{}
|
|
||||||
a.ExtendBaseWidget(a)
|
|
||||||
a.Im = &canvas.Image{FillMode: canvas.ImageFillContain, ScaleMode: canvas.ImageScaleFastest}
|
|
||||||
a.Im.SetMinSize(fyne.NewSize(200, 200))
|
|
||||||
a.playbtn = &canvas.Image{FillMode: canvas.ImageFillContain, Resource: res.ResPlaybuttonPng}
|
|
||||||
a.playbtn.SetMinSize(fyne.NewSize(60, 60))
|
|
||||||
a.playbtn.Hidden = true
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) CreateRenderer() fyne.WidgetRenderer {
|
|
||||||
return widget.NewSimpleRenderer(
|
|
||||||
container.NewMax(a.Im, container.NewCenter(a.playbtn)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) Cursor() desktop.Cursor {
|
|
||||||
return desktop.PointerCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) Tapped(e *fyne.PointEvent) {
|
|
||||||
if isInside(a.center(), a.playbtn.Size().Height/2, e.Position) {
|
|
||||||
if a.OnDoubleTapped != nil {
|
|
||||||
a.OnDoubleTapped()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if a.OnTapped != nil {
|
|
||||||
a.OnTapped()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) MouseIn(*desktop.MouseEvent) {
|
|
||||||
a.playbtn.Hidden = false
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) MouseOut() {
|
|
||||||
a.playbtn.Hidden = true
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: figure out why circle around play button isn't being displayed
|
|
||||||
func (a *albumCover) MouseMoved(e *desktop.MouseEvent) {
|
|
||||||
if isInside(a.center(), a.playbtn.MinSize().Height/2, e.Position) {
|
|
||||||
a.playbtn.SetMinSize(fyne.NewSize(65, 65))
|
|
||||||
} else {
|
|
||||||
a.playbtn.SetMinSize(fyne.NewSize(60, 60))
|
|
||||||
}
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) center() fyne.Position {
|
|
||||||
return fyne.NewPos(a.Size().Width/2, a.Size().Height/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) SetImage(im image.Image) {
|
|
||||||
a.Im.Resource = nil
|
|
||||||
a.Im.Image = im
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *albumCover) SetImageResource(res *fyne.StaticResource) {
|
|
||||||
a.Im.Image = nil
|
|
||||||
a.Im.Resource = res
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isInside(origin fyne.Position, radius float32, point fyne.Position) bool {
|
|
||||||
x, y := (point.X - origin.X), (point.Y - origin.Y)
|
|
||||||
return x*x+y*y <= radius*radius
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlbumCard struct {
|
|
||||||
widget.BaseWidget
|
|
||||||
|
|
||||||
albumID string
|
|
||||||
artistID string
|
|
||||||
title *CustomHyperlink
|
|
||||||
artist *CustomHyperlink
|
|
||||||
year *widget.Label
|
|
||||||
container *fyne.Container
|
|
||||||
|
|
||||||
showYear bool
|
|
||||||
|
|
||||||
// updated by AlbumGrid
|
|
||||||
Cover *albumCover
|
|
||||||
|
|
||||||
// these fields are used by AlbumGrid to track async update tasks
|
|
||||||
PrevAlbumID string
|
|
||||||
ImgLoadCancel context.CancelFunc
|
|
||||||
|
|
||||||
OnPlay func()
|
|
||||||
OnShowAlbumPage func()
|
|
||||||
OnShowArtistPage func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAlbumCard(showYear bool) *AlbumCard {
|
|
||||||
a := &AlbumCard{
|
|
||||||
title: NewCustomHyperlink(),
|
|
||||||
artist: NewCustomHyperlink(),
|
|
||||||
year: widget.NewLabel(""),
|
|
||||||
Cover: newAlbumCover(),
|
|
||||||
showYear: showYear,
|
|
||||||
}
|
|
||||||
a.ExtendBaseWidget(a)
|
|
||||||
a.Cover.OnDoubleTapped = func() {
|
|
||||||
if a.OnPlay != nil {
|
|
||||||
a.OnPlay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showAlbumFn := func() {
|
|
||||||
if a.OnShowAlbumPage != nil {
|
|
||||||
a.OnShowAlbumPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.Cover.OnTapped = showAlbumFn
|
|
||||||
a.title.OnTapped = showAlbumFn
|
|
||||||
a.artist.OnTapped = func() {
|
|
||||||
if a.OnShowArtistPage != nil {
|
|
||||||
a.OnShowArtistPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.createContainer()
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumCard) createContainer() {
|
|
||||||
var secondLabel fyne.Widget = a.artist
|
|
||||||
if a.showYear {
|
|
||||||
secondLabel = a.year
|
|
||||||
}
|
|
||||||
info := container.New(&layouts.VboxCustomPadding{ExtraPad: -16}, a.title, secondLabel)
|
|
||||||
c := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, a.Cover, info)
|
|
||||||
pad := &layouts.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
|
|
||||||
a.container = container.New(pad, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumCard) Update(al *subsonic.AlbumID3) {
|
|
||||||
a.title.SetText(al.Name)
|
|
||||||
a.artist.SetText(al.Artist)
|
|
||||||
a.year.SetText(strconv.Itoa(al.Year))
|
|
||||||
a.albumID = al.ID
|
|
||||||
a.artistID = al.ArtistID
|
|
||||||
a.Cover.playbtn.Hidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumCard) AlbumID() string {
|
|
||||||
return a.albumID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumCard) ArtistID() string {
|
|
||||||
return a.artistID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumCard) CreateRenderer() fyne.WidgetRenderer {
|
|
||||||
return widget.NewSimpleRenderer(a.container)
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
package widgets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"image"
|
|
||||||
"log"
|
|
||||||
"supersonic/backend"
|
|
||||||
"supersonic/res"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"github.com/dweymouth/go-subsonic/subsonic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const albumFetchBatchSize = 6
|
|
||||||
|
|
||||||
type ImageFetcher interface {
|
|
||||||
GetAlbumThumbnailFromCache(string) (image.Image, bool)
|
|
||||||
GetAlbumThumbnail(string) (image.Image, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlbumGrid struct {
|
|
||||||
widget.BaseWidget
|
|
||||||
|
|
||||||
AlbumGridState
|
|
||||||
|
|
||||||
grid *widget.GridWrapList
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlbumGridState struct {
|
|
||||||
albums []*subsonic.AlbumID3
|
|
||||||
albumsMutex sync.RWMutex
|
|
||||||
iter *backend.BatchingIterator
|
|
||||||
highestShown int
|
|
||||||
fetching bool
|
|
||||||
done bool
|
|
||||||
showYear bool
|
|
||||||
|
|
||||||
imageFetcher ImageFetcher
|
|
||||||
OnPlayAlbum func(string)
|
|
||||||
OnShowAlbumPage func(string)
|
|
||||||
OnShowArtistPage func(string)
|
|
||||||
|
|
||||||
scrollPos float32
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fyne.Widget = (*AlbumGrid)(nil)
|
|
||||||
|
|
||||||
func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *AlbumGrid {
|
|
||||||
ag := &AlbumGrid{
|
|
||||||
AlbumGridState: AlbumGridState{
|
|
||||||
albums: albums,
|
|
||||||
done: true,
|
|
||||||
imageFetcher: fetch,
|
|
||||||
showYear: showYear,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ag.ExtendBaseWidget(ag)
|
|
||||||
ag.createGridWrapList()
|
|
||||||
return ag
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher, showYear bool) *AlbumGrid {
|
|
||||||
ag := &AlbumGrid{
|
|
||||||
AlbumGridState: AlbumGridState{
|
|
||||||
iter: backend.NewBatchingIterator(iter),
|
|
||||||
imageFetcher: fetch,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ag.ExtendBaseWidget(ag)
|
|
||||||
|
|
||||||
ag.createGridWrapList()
|
|
||||||
|
|
||||||
// fetch initial albums
|
|
||||||
ag.fetchMoreAlbums(36)
|
|
||||||
return ag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlbumGrid) SaveToState() AlbumGridState {
|
|
||||||
s := ag.AlbumGridState
|
|
||||||
s.scrollPos = ag.grid.GetScrollOffset()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAlbumGridFromState(state AlbumGridState) *AlbumGrid {
|
|
||||||
ag := &AlbumGrid{AlbumGridState: state}
|
|
||||||
ag.ExtendBaseWidget(ag)
|
|
||||||
ag.createGridWrapList()
|
|
||||||
ag.Refresh() // needed to initialize the widget
|
|
||||||
ag.grid.ScrollToOffset(state.scrollPos)
|
|
||||||
return ag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlbumGrid) Clear() {
|
|
||||||
ag.albumsMutex.Lock()
|
|
||||||
defer ag.albumsMutex.Unlock()
|
|
||||||
ag.albums = nil
|
|
||||||
ag.done = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlbumGrid) Reset(iter backend.AlbumIterator) {
|
|
||||||
ag.albumsMutex.Lock()
|
|
||||||
ag.albums = nil
|
|
||||||
ag.albumsMutex.Unlock()
|
|
||||||
ag.fetching = false
|
|
||||||
ag.done = false
|
|
||||||
ag.highestShown = 0
|
|
||||||
ag.iter = backend.NewBatchingIterator(iter)
|
|
||||||
ag.fetchMoreAlbums(36)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlbumGrid) createGridWrapList() {
|
|
||||||
g := widget.NewGridWrapList(
|
|
||||||
func() int {
|
|
||||||
return ag.lenAlbums()
|
|
||||||
},
|
|
||||||
// create func
|
|
||||||
func() fyne.CanvasObject {
|
|
||||||
ac := NewAlbumCard(ag.showYear)
|
|
||||||
ac.OnPlay = func() {
|
|
||||||
if ag.OnPlayAlbum != nil {
|
|
||||||
ag.OnPlayAlbum(ac.AlbumID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ac.OnShowArtistPage = func() {
|
|
||||||
if ag.OnShowArtistPage != nil {
|
|
||||||
ag.OnShowArtistPage(ac.ArtistID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ac.OnShowAlbumPage = func() {
|
|
||||||
if ag.OnShowAlbumPage != nil {
|
|
||||||
ag.OnShowAlbumPage(ac.AlbumID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ac
|
|
||||||
},
|
|
||||||
// update func
|
|
||||||
func(itemID int, obj fyne.CanvasObject) {
|
|
||||||
ac := obj.(*AlbumCard)
|
|
||||||
ag.doUpdateAlbumCard(itemID, ac)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ag.grid = g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *AlbumCard) {
|
|
||||||
if albumIdx > ag.highestShown {
|
|
||||||
ag.highestShown = albumIdx
|
|
||||||
}
|
|
||||||
ag.albumsMutex.RLock()
|
|
||||||
album := ag.albums[albumIdx]
|
|
||||||
ag.albumsMutex.RUnlock()
|
|
||||||
if ac.PrevAlbumID == album.ID {
|
|
||||||
// nothing to do
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ac.Update(album)
|
|
||||||
ac.PrevAlbumID = album.ID
|
|
||||||
// cancel any previous image fetch
|
|
||||||
if ac.ImgLoadCancel != nil {
|
|
||||||
ac.ImgLoadCancel()
|
|
||||||
ac.ImgLoadCancel = nil
|
|
||||||
}
|
|
||||||
if img, ok := ag.imageFetcher.GetAlbumThumbnailFromCache(album.CoverArt); ok {
|
|
||||||
ac.Cover.SetImage(img)
|
|
||||||
} else {
|
|
||||||
ac.Cover.SetImageResource(res.ResAlbumplaceholderPng)
|
|
||||||
// asynchronously fetch cover image
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
ac.ImgLoadCancel = cancel
|
|
||||||
go func(ctx context.Context) {
|
|
||||||
i, err := ag.imageFetcher.GetAlbumThumbnail(album.CoverArt)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
if err == nil {
|
|
||||||
ac.Cover.SetImage(i)
|
|
||||||
} else {
|
|
||||||
log.Printf("error fetching image: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user has scrolled near the bottom, fetch more
|
|
||||||
if !ag.done && !ag.fetching && albumIdx > ag.lenAlbums()-10 {
|
|
||||||
ag.fetchMoreAlbums(20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumGrid) lenAlbums() int {
|
|
||||||
a.albumsMutex.RLock()
|
|
||||||
defer a.albumsMutex.RUnlock()
|
|
||||||
return len(a.albums)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetches at least count more albums
|
|
||||||
func (a *AlbumGrid) fetchMoreAlbums(count int) {
|
|
||||||
if a.iter == nil {
|
|
||||||
a.done = true
|
|
||||||
}
|
|
||||||
a.fetching = true
|
|
||||||
go func() {
|
|
||||||
// keep repeating the fetch task as long as the user
|
|
||||||
// has scrolled near the bottom
|
|
||||||
for !a.done && a.highestShown >= a.lenAlbums()-10 {
|
|
||||||
n := 0
|
|
||||||
for !a.done && n < count {
|
|
||||||
albums := a.iter.NextN(albumFetchBatchSize)
|
|
||||||
a.albumsMutex.Lock()
|
|
||||||
a.albums = append(a.albums, albums...)
|
|
||||||
a.albumsMutex.Unlock()
|
|
||||||
if len(albums) < albumFetchBatchSize {
|
|
||||||
a.done = true
|
|
||||||
}
|
|
||||||
n += len(albums)
|
|
||||||
if len(albums) > 0 {
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.fetching = false
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlbumGrid) CreateRenderer() fyne.WidgetRenderer {
|
|
||||||
return widget.NewSimpleRenderer(a.grid)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"supersonic/backend"
|
||||||
|
"supersonic/res"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/dweymouth/go-subsonic/subsonic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const albumFetchBatchSize = 6
|
||||||
|
|
||||||
|
type ImageFetcher interface {
|
||||||
|
GetCoverThumbnailFromCache(string) (image.Image, bool)
|
||||||
|
GetCoverThumbnail(string) (image.Image, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GridView struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
GridViewState
|
||||||
|
|
||||||
|
grid *widget.GridWrapList
|
||||||
|
}
|
||||||
|
|
||||||
|
type GridViewState struct {
|
||||||
|
items []*subsonic.AlbumID3
|
||||||
|
itemsMutex sync.RWMutex
|
||||||
|
iter *backend.BatchingIterator
|
||||||
|
highestShown int
|
||||||
|
fetching bool
|
||||||
|
done bool
|
||||||
|
showYear bool
|
||||||
|
|
||||||
|
imageFetcher ImageFetcher
|
||||||
|
OnPlay func(string)
|
||||||
|
OnShowItemPage func(string)
|
||||||
|
OnShowSecondaryPage func(string)
|
||||||
|
|
||||||
|
scrollPos float32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fyne.Widget = (*GridView)(nil)
|
||||||
|
|
||||||
|
func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *GridView {
|
||||||
|
g := &GridView{
|
||||||
|
GridViewState: GridViewState{
|
||||||
|
items: albums,
|
||||||
|
done: true,
|
||||||
|
imageFetcher: fetch,
|
||||||
|
showYear: showYear,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
g.ExtendBaseWidget(g)
|
||||||
|
g.createGridWrapList()
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher, showYear bool) *GridView {
|
||||||
|
g := &GridView{
|
||||||
|
GridViewState: GridViewState{
|
||||||
|
iter: backend.NewBatchingIterator(iter),
|
||||||
|
imageFetcher: fetch,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
g.ExtendBaseWidget(g)
|
||||||
|
|
||||||
|
g.createGridWrapList()
|
||||||
|
|
||||||
|
// fetch initial items
|
||||||
|
g.fetchMoreItems(36)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) SaveToState() GridViewState {
|
||||||
|
s := g.GridViewState
|
||||||
|
s.scrollPos = g.grid.GetScrollOffset()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGridViewFromState(state GridViewState) *GridView {
|
||||||
|
g := &GridView{GridViewState: state}
|
||||||
|
g.ExtendBaseWidget(g)
|
||||||
|
g.createGridWrapList()
|
||||||
|
g.Refresh() // needed to initialize the widget
|
||||||
|
g.grid.ScrollToOffset(state.scrollPos)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) Clear() {
|
||||||
|
g.itemsMutex.Lock()
|
||||||
|
defer g.itemsMutex.Unlock()
|
||||||
|
g.items = nil
|
||||||
|
g.done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) Reset(iter backend.AlbumIterator) {
|
||||||
|
g.itemsMutex.Lock()
|
||||||
|
g.items = nil
|
||||||
|
g.itemsMutex.Unlock()
|
||||||
|
g.fetching = false
|
||||||
|
g.done = false
|
||||||
|
g.highestShown = 0
|
||||||
|
g.iter = backend.NewBatchingIterator(iter)
|
||||||
|
g.fetchMoreItems(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) createGridWrapList() {
|
||||||
|
g.grid = widget.NewGridWrapList(
|
||||||
|
func() int {
|
||||||
|
return g.lenItems()
|
||||||
|
},
|
||||||
|
// create func
|
||||||
|
func() fyne.CanvasObject {
|
||||||
|
card := NewGridViewCard(g.showYear)
|
||||||
|
card.OnPlay = func() {
|
||||||
|
if g.OnPlay != nil {
|
||||||
|
g.OnPlay(card.ItemID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
card.OnShowSecondaryPage = func() {
|
||||||
|
if g.OnShowSecondaryPage != nil {
|
||||||
|
g.OnShowSecondaryPage(card.SecondaryID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
card.OnShowItemPage = func() {
|
||||||
|
if g.OnShowItemPage != nil {
|
||||||
|
g.OnShowItemPage(card.ItemID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return card
|
||||||
|
},
|
||||||
|
// update func
|
||||||
|
func(itemID int, obj fyne.CanvasObject) {
|
||||||
|
ac := obj.(*GridViewCard)
|
||||||
|
g.doUpdateItemCard(itemID, ac)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewCard) {
|
||||||
|
if itemIdx > g.highestShown {
|
||||||
|
g.highestShown = itemIdx
|
||||||
|
}
|
||||||
|
g.itemsMutex.RLock()
|
||||||
|
album := g.items[itemIdx]
|
||||||
|
g.itemsMutex.RUnlock()
|
||||||
|
if card.PrevID == album.ID {
|
||||||
|
// nothing to do
|
||||||
|
return
|
||||||
|
}
|
||||||
|
card.Update(album)
|
||||||
|
card.PrevID = album.ID
|
||||||
|
// cancel any previous image fetch
|
||||||
|
if card.ImgLoadCancel != nil {
|
||||||
|
card.ImgLoadCancel()
|
||||||
|
card.ImgLoadCancel = nil
|
||||||
|
}
|
||||||
|
if img, ok := g.imageFetcher.GetCoverThumbnailFromCache(album.CoverArt); ok {
|
||||||
|
card.Cover.SetImage(img)
|
||||||
|
} else {
|
||||||
|
card.Cover.SetImageResource(res.ResAlbumplaceholderPng)
|
||||||
|
// asynchronously fetch cover image
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
card.ImgLoadCancel = cancel
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
i, err := g.imageFetcher.GetCoverThumbnail(album.CoverArt)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if err == nil {
|
||||||
|
card.Cover.SetImage(i)
|
||||||
|
} else {
|
||||||
|
log.Printf("error fetching image: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user has scrolled near the bottom, fetch more
|
||||||
|
if !g.done && !g.fetching && itemIdx > g.lenItems()-10 {
|
||||||
|
g.fetchMoreItems(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) lenItems() int {
|
||||||
|
g.itemsMutex.RLock()
|
||||||
|
defer g.itemsMutex.RUnlock()
|
||||||
|
return len(g.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches at least count more items
|
||||||
|
func (g *GridView) fetchMoreItems(count int) {
|
||||||
|
if g.iter == nil {
|
||||||
|
g.done = true
|
||||||
|
}
|
||||||
|
g.fetching = true
|
||||||
|
go func() {
|
||||||
|
// keep repeating the fetch task as long as the user
|
||||||
|
// has scrolled near the bottom
|
||||||
|
for !g.done && g.highestShown >= g.lenItems()-10 {
|
||||||
|
n := 0
|
||||||
|
for !g.done && n < count {
|
||||||
|
albums := g.iter.NextN(albumFetchBatchSize)
|
||||||
|
g.itemsMutex.Lock()
|
||||||
|
g.items = append(g.items, albums...)
|
||||||
|
g.itemsMutex.Unlock()
|
||||||
|
if len(albums) < albumFetchBatchSize {
|
||||||
|
g.done = true
|
||||||
|
}
|
||||||
|
n += len(albums)
|
||||||
|
if len(albums) > 0 {
|
||||||
|
g.Refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.fetching = false
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(g.grid)
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"image"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"supersonic/res"
|
||||||
|
"supersonic/ui/layouts"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/driver/desktop"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
|
"github.com/dweymouth/go-subsonic/subsonic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ fyne.Widget = (*GridViewCard)(nil)
|
||||||
|
|
||||||
|
var _ fyne.Widget = (*coverImage)(nil)
|
||||||
|
var _ fyne.Tappable = (*coverImage)(nil)
|
||||||
|
|
||||||
|
type coverImage struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
Im *canvas.Image
|
||||||
|
playbtn *canvas.Image
|
||||||
|
OnDoubleTapped func()
|
||||||
|
OnTapped func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCoverImage() *coverImage {
|
||||||
|
c := &coverImage{}
|
||||||
|
c.ExtendBaseWidget(c)
|
||||||
|
c.Im = &canvas.Image{FillMode: canvas.ImageFillContain, ScaleMode: canvas.ImageScaleFastest}
|
||||||
|
c.Im.SetMinSize(fyne.NewSize(200, 200))
|
||||||
|
c.playbtn = &canvas.Image{FillMode: canvas.ImageFillContain, Resource: res.ResPlaybuttonPng}
|
||||||
|
c.playbtn.SetMinSize(fyne.NewSize(60, 60))
|
||||||
|
c.playbtn.Hidden = true
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *coverImage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(
|
||||||
|
container.NewMax(c.Im, container.NewCenter(c.playbtn)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *coverImage) Cursor() desktop.Cursor {
|
||||||
|
return desktop.PointerCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *coverImage) Tapped(e *fyne.PointEvent) {
|
||||||
|
if isInside(c.center(), c.playbtn.Size().Height/2, e.Position) {
|
||||||
|
if c.OnDoubleTapped != nil {
|
||||||
|
c.OnDoubleTapped()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.OnTapped != nil {
|
||||||
|
c.OnTapped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) MouseIn(*desktop.MouseEvent) {
|
||||||
|
a.playbtn.Hidden = false
|
||||||
|
a.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) MouseOut() {
|
||||||
|
a.playbtn.Hidden = true
|
||||||
|
a.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) MouseMoved(e *desktop.MouseEvent) {
|
||||||
|
if isInside(a.center(), a.playbtn.MinSize().Height/2, e.Position) {
|
||||||
|
a.playbtn.SetMinSize(fyne.NewSize(65, 65))
|
||||||
|
} else {
|
||||||
|
a.playbtn.SetMinSize(fyne.NewSize(60, 60))
|
||||||
|
}
|
||||||
|
a.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) center() fyne.Position {
|
||||||
|
return fyne.NewPos(a.Size().Width/2, a.Size().Height/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) SetImage(im image.Image) {
|
||||||
|
a.Im.Resource = nil
|
||||||
|
a.Im.Image = im
|
||||||
|
a.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *coverImage) SetImageResource(res *fyne.StaticResource) {
|
||||||
|
a.Im.Image = nil
|
||||||
|
a.Im.Resource = res
|
||||||
|
a.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInside(origin fyne.Position, radius float32, point fyne.Position) bool {
|
||||||
|
x, y := (point.X - origin.X), (point.Y - origin.Y)
|
||||||
|
return x*x+y*y <= radius*radius
|
||||||
|
}
|
||||||
|
|
||||||
|
type GridViewCardModel struct {
|
||||||
|
Name string
|
||||||
|
ID string
|
||||||
|
CoverArtID string
|
||||||
|
Secondary string
|
||||||
|
SecondaryID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GridViewCard struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
albumID string
|
||||||
|
artistID string
|
||||||
|
title *CustomHyperlink
|
||||||
|
artist *CustomHyperlink
|
||||||
|
year *widget.Label
|
||||||
|
container *fyne.Container
|
||||||
|
|
||||||
|
showYear bool
|
||||||
|
|
||||||
|
// updated by GridView
|
||||||
|
Cover *coverImage
|
||||||
|
|
||||||
|
// these fields are used by GridView to track async update tasks
|
||||||
|
PrevID string
|
||||||
|
ImgLoadCancel context.CancelFunc
|
||||||
|
|
||||||
|
OnPlay func()
|
||||||
|
OnShowItemPage func()
|
||||||
|
OnShowSecondaryPage func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGridViewCard(showYear bool) *GridViewCard {
|
||||||
|
g := &GridViewCard{
|
||||||
|
title: NewCustomHyperlink(),
|
||||||
|
artist: NewCustomHyperlink(),
|
||||||
|
year: widget.NewLabel(""),
|
||||||
|
Cover: newCoverImage(),
|
||||||
|
showYear: showYear,
|
||||||
|
}
|
||||||
|
g.ExtendBaseWidget(g)
|
||||||
|
g.Cover.OnDoubleTapped = func() {
|
||||||
|
if g.OnPlay != nil {
|
||||||
|
g.OnPlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showItemFn := func() {
|
||||||
|
if g.OnShowItemPage != nil {
|
||||||
|
g.OnShowItemPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Cover.OnTapped = showItemFn
|
||||||
|
g.title.OnTapped = showItemFn
|
||||||
|
g.artist.OnTapped = func() {
|
||||||
|
if g.OnShowSecondaryPage != nil {
|
||||||
|
g.OnShowSecondaryPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.createContainer()
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridViewCard) createContainer() {
|
||||||
|
var secondLabel fyne.Widget = g.artist
|
||||||
|
if g.showYear {
|
||||||
|
secondLabel = g.year
|
||||||
|
}
|
||||||
|
info := container.New(&layouts.VboxCustomPadding{ExtraPad: -16}, g.title, secondLabel)
|
||||||
|
c := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, g.Cover, info)
|
||||||
|
pad := &layouts.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
|
||||||
|
g.container = container.New(pad, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridViewCard) Update(al *subsonic.AlbumID3) {
|
||||||
|
g.title.SetText(al.Name)
|
||||||
|
g.artist.SetText(al.Artist)
|
||||||
|
g.year.SetText(strconv.Itoa(al.Year))
|
||||||
|
g.albumID = al.ID
|
||||||
|
g.artistID = al.ArtistID
|
||||||
|
g.Cover.playbtn.Hidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridViewCard) ItemID() string {
|
||||||
|
return g.albumID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridViewCard) SecondaryID() string {
|
||||||
|
return g.artistID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridViewCard) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(g.container)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user