From 96f0748767f347429cb7f4360f3f8a6739ee82d7 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 21 Jan 2023 17:31:52 -0800 Subject: [PATCH] add Favorites page and toggle favorite button to album page --- ui/browsing/albumpage.go | 37 ++++++++++--- ui/browsing/favoritespage.go | 104 +++++++++++++++++++++++++++++++++++ ui/browsing/router.go | 9 ++- ui/mainwindow.go | 3 + ui/widgets/favoritebutton.go | 40 ++++++++++++++ 5 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 ui/browsing/favoritespage.go create mode 100644 ui/widgets/favoritebutton.go diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 6355aa0..8e30f50 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -20,6 +20,7 @@ type AlbumPage struct { widget.BaseWidget albumID string + sm *backend.ServerManager im *backend.ImageManager lm *backend.LibraryManager nav func(Route) @@ -31,8 +32,8 @@ type AlbumPage struct { OnPlayAlbum func(string, int) } -func NewAlbumPage(albumID string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumPage { - a := &AlbumPage{albumID: albumID, lm: lm, im: im, nav: nav} +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} a.ExtendBaseWidget(a) a.header = NewAlbumPageHeader(a) a.tracklist = widgets.NewTracklist(nil) @@ -104,19 +105,22 @@ type AlbumPageHeader struct { artistID string genre string + page *AlbumPage + cover *canvas.Image titleLabel *widget.RichText artistLabel *widgets.CustomHyperlink genreLabel *widgets.CustomHyperlink miscLabel *widget.Label - playButton *widget.Button + toggleFavButton *widgets.FavoriteButton + playButton *widget.Button container *fyne.Container } func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { - a := &AlbumPageHeader{} + a := &AlbumPageHeader{page: page} a.ExtendBaseWidget(a) a.cover = &canvas.Image{FillMode: canvas.ImageFillContain} a.cover.SetMinSize(fyne.NewSize(225, 225)) @@ -142,12 +146,19 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { a.playButton = widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { 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, - container.NewVBox( + container.New(&layouts.VboxCustomPadding{ExtraPad: -10}, a.titleLabel, - container.New(&layouts.VboxCustomPadding{ExtraPad: -10}, a.artistLabel, a.genreLabel, a.miscLabel), - container.NewHBox(a.playButton), + container.NewVBox( + 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 @@ -165,6 +176,7 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana a.genre = album.Genre a.genreLabel.SetText(album.Genre) a.miscLabel.SetText(formatMiscLabelStr(album)) + a.toggleFavButton.IsFavorited = !album.Starred.IsZero() a.Refresh() // 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 { 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 lm *backend.LibraryManager im *backend.ImageManager + sm *backend.ServerManager nav func(Route) } 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) } diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go new file mode 100644 index 0000000..8adfce4 --- /dev/null +++ b/ui/browsing/favoritespage.go @@ -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) +} diff --git a/ui/browsing/router.go b/ui/browsing/router.go index d87af84..e5be4ed 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -14,6 +14,7 @@ const ( Artists Genre Genres + Favorites Playlist Playlists ) @@ -35,6 +36,10 @@ func AlbumRoute(albumID string) Route { return Route{Page: Album, Arg: albumID} } +func FavoritesRoute() Route { + return Route{Page: Favorites} +} + func GenreRoute(genre string) Route { 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 { switch rte.Page { 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: return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Artist: return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute) case Artists: return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute) + case Favorites: + return NewFavoritesPage(r.App.ServerManager, r.App.ImageManager, r.OpenRoute) case Genre: return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genres: diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 21483db..aeba693 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -74,6 +74,9 @@ func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string } func (m *MainWindow) addNavigationButtons() { + m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() { + m.Router.OpenRoute(browsing.FavoritesRoute()) + }) m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() { m.Router.OpenRoute(browsing.AlbumsRoute(backend.AlbumSortRecentlyAdded)) }) diff --git a/ui/widgets/favoritebutton.go b/ui/widgets/favoritebutton.go new file mode 100644 index 0000000..942ffac --- /dev/null +++ b/ui/widgets/favoritebutton.go @@ -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() +}