diff --git a/backend/imagemanager.go b/backend/imagemanager.go index b3a2c53..3858efe 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -18,6 +18,9 @@ type ImageManager struct { s *ServerManager baseCacheDir string thumbnailCache gcache.Cache + + cachedFullSizeCover image.Image + cachedFullSizeCoverID string } func NewImageManager(s *ServerManager, baseCacheDir string) *ImageManager { @@ -66,6 +69,19 @@ func (i *ImageManager) GetAlbumThumbnail(albumID string) (image.Image, error) { return i.fetchAndCacheCoverFromServer(albumID) } +func (i *ImageManager) GetFullSizeAlbumCover(albumID string) (image.Image, error) { + if i.cachedFullSizeCoverID == albumID { + return i.cachedFullSizeCover, nil + } + im, err := i.s.Server.GetCoverArt(albumID, nil) + if err != nil { + return nil, err + } + i.cachedFullSizeCover = im + i.cachedFullSizeCoverID = albumID + return im, nil +} + func (i *ImageManager) ensureCoverCacheDir() string { path := path.Join(i.baseCacheDir, i.s.ServerID.String(), "covers") configdir.MakePath(path) diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 8e30f50..6280024 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -19,21 +19,34 @@ import ( type AlbumPage struct { widget.BaseWidget - albumID string - sm *backend.ServerManager - im *backend.ImageManager - lm *backend.LibraryManager - nav func(Route) - header *AlbumPageHeader - tracklist *widgets.Tracklist - nowPlayingID string - container *fyne.Container + albumID string + sm *backend.ServerManager + im *backend.ImageManager + lm *backend.LibraryManager + nav func(Route) + header *AlbumPageHeader + tracklist *widgets.Tracklist + nowPlayingID string + container *fyne.Container + popUpProvider PopUpProvider OnPlayAlbum func(string, int) } -func NewAlbumPage(albumID string, sm *backend.ServerManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumPage { - a := &AlbumPage{albumID: albumID, sm: sm, lm: lm, im: im, nav: nav} +type PopUpProvider interface { + CreatePopUp(fyne.CanvasObject) *widget.PopUp + WindowSize() fyne.Size +} + +func NewAlbumPage( + albumID string, + sm *backend.ServerManager, + lm *backend.LibraryManager, + im *backend.ImageManager, + popUpProvider PopUpProvider, + nav func(Route), +) *AlbumPage { + a := &AlbumPage{albumID: albumID, sm: sm, lm: lm, im: im, nav: nav, popUpProvider: popUpProvider} a.ExtendBaseWidget(a) a.header = NewAlbumPageHeader(a) a.tracklist = widgets.NewTracklist(nil) @@ -55,10 +68,11 @@ func (a *AlbumPage) SetPlayAlbumCallback(cb func(string, int)) { func (a *AlbumPage) Save() SavedPage { return &savedAlbumPage{ - albumID: a.albumID, - lm: a.lm, - im: a.im, - nav: a.nav, + albumID: a.albumID, + lm: a.lm, + im: a.im, + nav: a.nav, + popUpProvider: a.popUpProvider, } } @@ -107,7 +121,7 @@ type AlbumPageHeader struct { page *AlbumPage - cover *canvas.Image + cover *widgets.TappableImage titleLabel *widget.RichText artistLabel *widgets.CustomHyperlink genreLabel *widgets.CustomHyperlink @@ -122,12 +136,14 @@ type AlbumPageHeader struct { func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { a := &AlbumPageHeader{page: page} a.ExtendBaseWidget(a) - a.cover = &canvas.Image{FillMode: canvas.ImageFillContain} + a.cover = widgets.NewTappableImage() + a.cover.FillMode = canvas.ImageFillContain + a.cover.OnTapped = a.showPopUpCover a.cover.SetMinSize(fyne.NewSize(225, 225)) // due to cache warming we can probably immediately set the cover // and not have to set it asynchronously in the Update function if im, ok := page.im.GetAlbumThumbnailFromCache(page.albumID); ok { - a.cover.Image = im + a.cover.Image.Image = im } a.titleLabel = widget.NewRichTextWithText("") a.titleLabel.Wrapping = fyne.TextTruncate @@ -180,12 +196,12 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana a.Refresh() // cover image was already loaded from cache in consructor - if a.albumID == album.ID && a.cover.Image != nil { + if a.albumID == album.ID && a.cover.Image.Image != nil { return } go func() { if cover, err := im.GetAlbumThumbnail(album.ID); err == nil { - a.cover.Image = cover + a.cover.Image.Image = cover a.cover.Refresh() } else { log.Printf("error fetching cover: %v", err) @@ -201,18 +217,33 @@ func (a *AlbumPageHeader) toggleFavorited() { } } +func (a *AlbumPageHeader) showPopUpCover() { + cover, err := a.page.im.GetFullSizeAlbumCover(a.albumID) + if err != nil { + log.Printf("error getting full size album cover: %s", err.Error()) + return + } + im := canvas.NewImageFromImage(cover) + im.FillMode = canvas.ImageFillContain + pop := a.page.popUpProvider.CreatePopUp(im) + s := a.page.popUpProvider.WindowSize() + pop.Resize(fyne.NewSize(s.Width*0.8, s.Height*0.8)) + pop.ShowAtPosition(fyne.NewPos(0.1*s.Width, 0.1*s.Height)) +} + func formatMiscLabelStr(a *subsonic.AlbumID3) string { return fmt.Sprintf("%d · %d tracks · %s", a.Year, a.SongCount, util.SecondsToTimeString(float64(a.Duration))) } type savedAlbumPage struct { - albumID string - lm *backend.LibraryManager - im *backend.ImageManager - sm *backend.ServerManager - nav func(Route) + albumID string + lm *backend.LibraryManager + im *backend.ImageManager + sm *backend.ServerManager + popUpProvider PopUpProvider + nav func(Route) } func (s *savedAlbumPage) Restore() Page { - return NewAlbumPage(s.albumID, s.sm, s.lm, s.im, s.nav) + return NewAlbumPage(s.albumID, s.sm, s.lm, s.im, s.popUpProvider, s.nav) } diff --git a/ui/browsing/router.go b/ui/browsing/router.go index e5be4ed..dc7b8e0 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -2,6 +2,9 @@ package browsing import ( "supersonic/backend" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" ) type PageName int @@ -57,21 +60,39 @@ type NavigationHandler interface { } type Router struct { - App *backend.App - Nav NavigationHandler + App *backend.App + MainWindow fyne.Window + Nav NavigationHandler } -func NewRouter(app *backend.App, nav NavigationHandler) Router { +func NewRouter(app *backend.App, mainWindow fyne.Window, nav NavigationHandler) Router { return Router{ - App: app, - Nav: nav, + App: app, + MainWindow: mainWindow, + Nav: nav, } } +type popUpProvider struct { + window fyne.Window +} + +func (p *popUpProvider) CreatePopUp(obj fyne.CanvasObject) *widget.PopUp { + return widget.NewPopUp(obj, p.window.Canvas()) +} + +func (p *popUpProvider) WindowSize() fyne.Size { + return p.window.Canvas().Size() +} + +func (r Router) pop() *popUpProvider { + return &popUpProvider{window: r.MainWindow} +} + func (r Router) CreatePage(rte Route) Page { switch rte.Page { case Album: - return NewAlbumPage(rte.Arg, r.App.ServerManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + return NewAlbumPage(rte.Arg, r.App.ServerManager, r.App.LibraryManager, r.App.ImageManager, r.pop(), r.OpenRoute) case Albums: return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Artist: diff --git a/ui/mainwindow.go b/ui/mainwindow.go index a24a380..09d9f28 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -42,7 +42,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo BrowsingPane: browsing.NewBrowsingPane(app), } - m.Router = browsing.NewRouter(app, m.BrowsingPane) + m.Router = browsing.NewRouter(app, m.Window, m.BrowsingPane) m.BottomPanel = NewBottomPanel(app.Player, m.Router.OpenRoute) m.BottomPanel.SetPlaybackManager(app.PlaybackManager) m.BottomPanel.ImageManager = app.ImageManager diff --git a/ui/widgets/tappableimage.go b/ui/widgets/tappableimage.go new file mode 100644 index 0000000..a8a9608 --- /dev/null +++ b/ui/widgets/tappableimage.go @@ -0,0 +1,45 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/widget" +) + +type TappableImage struct { + widget.BaseWidget + canvas.Image + + OnTapped func() +} + +func NewTappableImage() *TappableImage { + t := &TappableImage{} + t.ExtendBaseWidget(t) + return t +} + +func (t *TappableImage) Cursor() desktop.Cursor { + return desktop.PointerCursor +} + +func (t *TappableImage) Tapped(e *fyne.PointEvent) { + if t.OnTapped != nil { + t.OnTapped() + } +} + +func (t *TappableImage) Refresh() { + t.Image.Refresh() + t.BaseWidget.Refresh() +} + +func (t *TappableImage) Resize(size fyne.Size) { + t.Image.Resize(size) + t.BaseWidget.Resize(size) +} + +func (t *TappableImage) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(&t.Image) +}