add Favorites page and toggle favorite button to album page
This commit is contained in:
@@ -20,6 +20,7 @@ type AlbumPage struct {
|
|||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
albumID string
|
albumID string
|
||||||
|
sm *backend.ServerManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
nav func(Route)
|
nav func(Route)
|
||||||
@@ -31,8 +32,8 @@ type AlbumPage struct {
|
|||||||
OnPlayAlbum func(string, int)
|
OnPlayAlbum func(string, int)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlbumPage(albumID string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumPage {
|
func NewAlbumPage(albumID string, sm *backend.ServerManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumPage {
|
||||||
a := &AlbumPage{albumID: albumID, lm: lm, im: im, nav: nav}
|
a := &AlbumPage{albumID: albumID, sm: sm, lm: lm, im: im, nav: nav}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.header = NewAlbumPageHeader(a)
|
a.header = NewAlbumPageHeader(a)
|
||||||
a.tracklist = widgets.NewTracklist(nil)
|
a.tracklist = widgets.NewTracklist(nil)
|
||||||
@@ -104,19 +105,22 @@ type AlbumPageHeader struct {
|
|||||||
artistID string
|
artistID string
|
||||||
genre string
|
genre string
|
||||||
|
|
||||||
|
page *AlbumPage
|
||||||
|
|
||||||
cover *canvas.Image
|
cover *canvas.Image
|
||||||
titleLabel *widget.RichText
|
titleLabel *widget.RichText
|
||||||
artistLabel *widgets.CustomHyperlink
|
artistLabel *widgets.CustomHyperlink
|
||||||
genreLabel *widgets.CustomHyperlink
|
genreLabel *widgets.CustomHyperlink
|
||||||
miscLabel *widget.Label
|
miscLabel *widget.Label
|
||||||
|
|
||||||
|
toggleFavButton *widgets.FavoriteButton
|
||||||
playButton *widget.Button
|
playButton *widget.Button
|
||||||
|
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
|
func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
|
||||||
a := &AlbumPageHeader{}
|
a := &AlbumPageHeader{page: page}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.cover = &canvas.Image{FillMode: canvas.ImageFillContain}
|
a.cover = &canvas.Image{FillMode: canvas.ImageFillContain}
|
||||||
a.cover.SetMinSize(fyne.NewSize(225, 225))
|
a.cover.SetMinSize(fyne.NewSize(225, 225))
|
||||||
@@ -142,12 +146,19 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
|
|||||||
a.playButton = widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() {
|
a.playButton = widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() {
|
||||||
page.onPlayTrackAt(0)
|
page.onPlayTrackAt(0)
|
||||||
})
|
})
|
||||||
|
a.toggleFavButton = widgets.NewFavoriteButton(a.toggleFavorited)
|
||||||
|
|
||||||
|
// Todo: there's got to be a way to make this less convoluted. Custom layout?
|
||||||
a.container = container.NewBorder(nil, nil, a.cover, nil,
|
a.container = container.NewBorder(nil, nil, a.cover, nil,
|
||||||
container.NewVBox(
|
container.New(&layouts.VboxCustomPadding{ExtraPad: -10},
|
||||||
a.titleLabel,
|
a.titleLabel,
|
||||||
container.New(&layouts.VboxCustomPadding{ExtraPad: -10}, a.artistLabel, a.genreLabel, a.miscLabel),
|
container.NewVBox(
|
||||||
container.NewHBox(a.playButton),
|
container.New(&layouts.VboxCustomPadding{ExtraPad: -12}, a.artistLabel, a.genreLabel, a.miscLabel),
|
||||||
|
container.NewVBox(
|
||||||
|
container.NewHBox(widgets.NewHSpace(2), a.playButton),
|
||||||
|
container.NewHBox(widgets.NewHSpace(2), a.toggleFavButton),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return a
|
return a
|
||||||
@@ -165,6 +176,7 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana
|
|||||||
a.genre = album.Genre
|
a.genre = album.Genre
|
||||||
a.genreLabel.SetText(album.Genre)
|
a.genreLabel.SetText(album.Genre)
|
||||||
a.miscLabel.SetText(formatMiscLabelStr(album))
|
a.miscLabel.SetText(formatMiscLabelStr(album))
|
||||||
|
a.toggleFavButton.IsFavorited = !album.Starred.IsZero()
|
||||||
a.Refresh()
|
a.Refresh()
|
||||||
|
|
||||||
// cover image was already loaded from cache in consructor
|
// cover image was already loaded from cache in consructor
|
||||||
@@ -181,6 +193,14 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AlbumPageHeader) toggleFavorited() {
|
||||||
|
if a.toggleFavButton.IsFavorited {
|
||||||
|
a.page.sm.Server.Star(subsonic.StarParameters{AlbumIDs: []string{a.albumID}})
|
||||||
|
} else {
|
||||||
|
a.page.sm.Server.Unstar(subsonic.StarParameters{AlbumIDs: []string{a.albumID}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func formatMiscLabelStr(a *subsonic.AlbumID3) string {
|
func formatMiscLabelStr(a *subsonic.AlbumID3) string {
|
||||||
return fmt.Sprintf("%d · %d tracks · %s", a.Year, a.SongCount, util.SecondsToTimeString(float64(a.Duration)))
|
return fmt.Sprintf("%d · %d tracks · %s", a.Year, a.SongCount, util.SecondsToTimeString(float64(a.Duration)))
|
||||||
}
|
}
|
||||||
@@ -189,9 +209,10 @@ type savedAlbumPage struct {
|
|||||||
albumID string
|
albumID string
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
|
sm *backend.ServerManager
|
||||||
nav func(Route)
|
nav func(Route)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedAlbumPage) Restore() Page {
|
func (s *savedAlbumPage) Restore() Page {
|
||||||
return NewAlbumPage(s.albumID, s.lm, s.im, s.nav)
|
return NewAlbumPage(s.albumID, s.sm, s.lm, s.im, s.nav)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package browsing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"supersonic/backend"
|
||||||
|
"supersonic/ui/widgets"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FavoritesPage struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
im *backend.ImageManager
|
||||||
|
sm *backend.ServerManager
|
||||||
|
nav func(Route)
|
||||||
|
grid *widgets.AlbumGrid
|
||||||
|
titleDisp *widget.RichText
|
||||||
|
container *fyne.Container
|
||||||
|
|
||||||
|
OnPlayAlbum func(string, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFavoritesPage(sm *backend.ServerManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage {
|
||||||
|
a := &FavoritesPage{
|
||||||
|
sm: sm,
|
||||||
|
im: im,
|
||||||
|
nav: nav,
|
||||||
|
}
|
||||||
|
a.ExtendBaseWidget(a)
|
||||||
|
a.titleDisp = widget.NewRichTextWithText("Favorites")
|
||||||
|
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
|
||||||
|
SizeName: theme.SizeNameHeadingText,
|
||||||
|
}
|
||||||
|
a.container = container.NewBorder(
|
||||||
|
container.NewHBox(widgets.NewHSpace(9), a.titleDisp), nil, nil, nil, layout.NewSpacer())
|
||||||
|
a.loadAsync()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) Route() Route {
|
||||||
|
return FavoritesRoute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) SetPlayAlbumCallback(cb func(string, int)) {
|
||||||
|
a.OnPlayAlbum = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) Reload() {
|
||||||
|
a.loadAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) Save() SavedPage {
|
||||||
|
return &savedFavoritesPage{
|
||||||
|
sm: a.sm,
|
||||||
|
im: a.im,
|
||||||
|
nav: a.nav,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) onPlayAlbum(albumID string) {
|
||||||
|
if a.OnPlayAlbum != nil {
|
||||||
|
a.OnPlayAlbum(albumID, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) onShowAlbumPage(albumID string) {
|
||||||
|
a.nav(AlbumRoute(albumID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) loadAsync() {
|
||||||
|
go func() {
|
||||||
|
starred, err := a.sm.Server.GetStarred2(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get favorites: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ag := widgets.NewFixedAlbumGrid(starred.Album, a.im, true /*showYear*/)
|
||||||
|
ag.OnPlayAlbum = a.onPlayAlbum
|
||||||
|
ag.OnShowAlbumPage = a.onShowAlbumPage
|
||||||
|
a.container.Objects[0] = ag
|
||||||
|
a.container.Refresh()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
a.ExtendBaseWidget(a)
|
||||||
|
return widget.NewSimpleRenderer(a.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
type savedFavoritesPage struct {
|
||||||
|
artistID string
|
||||||
|
sm *backend.ServerManager
|
||||||
|
im *backend.ImageManager
|
||||||
|
nav func(Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *savedFavoritesPage) Restore() Page {
|
||||||
|
return NewFavoritesPage(s.sm, s.im, s.nav)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ const (
|
|||||||
Artists
|
Artists
|
||||||
Genre
|
Genre
|
||||||
Genres
|
Genres
|
||||||
|
Favorites
|
||||||
Playlist
|
Playlist
|
||||||
Playlists
|
Playlists
|
||||||
)
|
)
|
||||||
@@ -35,6 +36,10 @@ func AlbumRoute(albumID string) Route {
|
|||||||
return Route{Page: Album, Arg: albumID}
|
return Route{Page: Album, Arg: albumID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FavoritesRoute() Route {
|
||||||
|
return Route{Page: Favorites}
|
||||||
|
}
|
||||||
|
|
||||||
func GenreRoute(genre string) Route {
|
func GenreRoute(genre string) Route {
|
||||||
return Route{Page: Genre, Arg: genre}
|
return Route{Page: Genre, Arg: genre}
|
||||||
}
|
}
|
||||||
@@ -66,13 +71,15 @@ func NewRouter(app *backend.App, nav NavigationHandler) Router {
|
|||||||
func (r Router) CreatePage(rte Route) Page {
|
func (r Router) CreatePage(rte Route) Page {
|
||||||
switch rte.Page {
|
switch rte.Page {
|
||||||
case Album:
|
case Album:
|
||||||
return NewAlbumPage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
return NewAlbumPage(rte.Arg, r.App.ServerManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
||||||
case Albums:
|
case Albums:
|
||||||
return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
||||||
case Artist:
|
case Artist:
|
||||||
return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute)
|
return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute)
|
||||||
case Artists:
|
case Artists:
|
||||||
return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute)
|
return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute)
|
||||||
|
case Favorites:
|
||||||
|
return NewFavoritesPage(r.App.ServerManager, r.App.ImageManager, r.OpenRoute)
|
||||||
case Genre:
|
case Genre:
|
||||||
return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
||||||
case Genres:
|
case Genres:
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MainWindow) addNavigationButtons() {
|
func (m *MainWindow) addNavigationButtons() {
|
||||||
|
m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() {
|
||||||
|
m.Router.OpenRoute(browsing.FavoritesRoute())
|
||||||
|
})
|
||||||
m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() {
|
m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() {
|
||||||
m.Router.OpenRoute(browsing.AlbumsRoute(backend.AlbumSortRecentlyAdded))
|
m.Router.OpenRoute(browsing.AlbumsRoute(backend.AlbumSortRecentlyAdded))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"supersonic/res"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FavoriteButton struct {
|
||||||
|
widget.Button
|
||||||
|
|
||||||
|
IsFavorited bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFavoriteButton(onTapped func()) *FavoriteButton {
|
||||||
|
f := &FavoriteButton{
|
||||||
|
Button: widget.Button{
|
||||||
|
OnTapped: onTapped,
|
||||||
|
Icon: res.ResHeartOutlineInvertPng,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f.ExtendBaseWidget(f)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FavoriteButton) Tapped(e *fyne.PointEvent) {
|
||||||
|
f.IsFavorited = !f.IsFavorited
|
||||||
|
f.Button.Tapped(e)
|
||||||
|
f.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FavoriteButton) Refresh() {
|
||||||
|
if f.IsFavorited {
|
||||||
|
f.Icon = res.ResHeartFilledInvertPng
|
||||||
|
} else {
|
||||||
|
f.Icon = res.ResHeartOutlineInvertPng
|
||||||
|
}
|
||||||
|
f.Button.Refresh()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user