diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index c4a703a..3966002 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -170,8 +170,8 @@ func (p *PlaybackManager) LoadTracks(tracks []*subsonic.Child, appendToQueue, sh return nil } -func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int) error { - if err := p.LoadAlbum(albumID, false, false); err != nil { +func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool) error { + if err := p.LoadAlbum(albumID, false, shuffle); err != nil { return err } if firstTrack <= 0 { @@ -180,8 +180,8 @@ func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int) error { return p.player.PlayTrackAt(firstTrack) } -func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int) error { - if err := p.LoadPlaylist(playlistID, false, false); err != nil { +func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int, shuffle bool) error { + if err := p.LoadPlaylist(playlistID, false, shuffle); err != nil { return err } if firstTrack <= 0 { diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 9f483a9..5dfade6 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -168,7 +168,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { } a.miscLabel = widget.NewLabel("") playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { - go page.pm.PlayAlbum(page.albumID, 0) + go page.pm.PlayAlbum(page.albumID, 0, false) }) shuffleBtn := widget.NewButtonWithIcon(" Shuffle", res.ResShuffleInvertSvg, func() { page.pm.LoadTracks(page.tracklist.Tracks, false, true) diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 121abe2..7f9cbcf 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -73,9 +73,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, a.sortOrder.Selected = cfg.SortOrder iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) - a.grid.OnPlay = a.onPlayAlbum - a.grid.OnShowSecondaryPage = a.onShowArtistPage - a.grid.OnShowItemPage = a.onShowAlbumPage + contr.ConnectAlbumGridActions(a.grid) a.searcher = widgets.NewSearcher() a.searcher.OnSearched = a.OnSearched a.createContainer(false) @@ -182,9 +180,7 @@ func (a *AlbumsPage) Save() SavedPage { func (a *AlbumsPage) doSearch(query string) { if a.searchGrid == nil { a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(a.lm.SearchIter(query)), a.im) - a.searchGrid.OnPlay = a.onPlayAlbum - a.searchGrid.OnShowItemPage = a.onShowAlbumPage - a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage + a.contr.ConnectAlbumGridActions(a.searchGrid) } else { a.searchGrid.Reset(widgets.NewGridViewAlbumIterator(a.lm.SearchIter(query))) } @@ -192,18 +188,6 @@ func (a *AlbumsPage) doSearch(query string) { a.Refresh() } -func (a *AlbumsPage) onPlayAlbum(albumID string) { - go a.pm.PlayAlbum(albumID, 0) -} - -func (a *AlbumsPage) onShowArtistPage(artistID string) { - a.contr.NavigateTo(controller.ArtistRoute(artistID)) -} - -func (a *AlbumsPage) onShowAlbumPage(albumID string) { - a.contr.NavigateTo(controller.AlbumRoute(albumID)) -} - func (a *AlbumsPage) onSortOrderChanged(order string) { a.cfg.SortOrder = a.sortOrder.Selected iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order)) diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 7eac001..fa53b9d 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -124,10 +124,6 @@ func (a *ArtistPage) OnSongChange(track *subsonic.Child, lastScrobbledIfAny *sub } } -func (a *ArtistPage) onPlayAlbum(albumID string) { - a.pm.PlayAlbum(albumID, 0) -} - func (a *ArtistPage) playAllTracks() { if a.artistInfo != nil { // page loaded for i, album := range a.artistInfo.Album { @@ -141,10 +137,6 @@ func (a *ArtistPage) playArtistRadio() { go a.pm.PlaySimilarSongs(a.artistID) } -func (a *ArtistPage) onShowAlbumPage(albumID string) { - a.contr.NavigateTo(controller.AlbumRoute(albumID)) -} - // should be called asynchronously func (a *ArtistPage) load() { artist, err := a.sm.Server.GetArtist(a.artistID) @@ -182,8 +174,7 @@ func (a *ArtistPage) showAlbumGrid() { } }) a.albumGrid = widgets.NewFixedGridView(model, a.im) - a.albumGrid.OnPlay = a.onPlayAlbum - a.albumGrid.OnShowItemPage = a.onShowAlbumPage + a.contr.ConnectAlbumGridActions(a.albumGrid) } a.container.Objects[0].(*fyne.Container).Objects[0] = a.albumGrid a.container.Objects[0].Refresh() diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 389682c..5ebbaa7 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -54,7 +54,7 @@ func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Contro a.ExtendBaseWidget(a) a.createHeader(0, "") a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(lm.StarredIter()), a.im) - a.connectGridActions() + a.contr.ConnectAlbumGridActions(a.grid) if cfg.InitialView == "Artists" { a.toggleBtns.SetActivatedButton(1) a.onShowFavoriteArtists() @@ -81,12 +81,6 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) { a.searcher.Entry.Text = searchText } -func (a *FavoritesPage) connectGridActions() { - a.grid.OnPlay = a.onPlayAlbum - a.grid.OnShowItemPage = a.onShowAlbumPage - a.grid.OnShowSecondaryPage = a.onShowArtistPage -} - func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) { searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer()) a.container = container.NewBorder( @@ -106,7 +100,6 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { a.ExtendBaseWidget(a) a.createHeader(saved.activeToggleBtn, saved.searchText) a.grid = widgets.NewGridViewFromState(saved.gridState) - a.connectGridActions() if saved.searchText != "" { a.searchGrid = widgets.NewGridViewFromState(saved.searchGridState) @@ -221,9 +214,7 @@ func (a *FavoritesPage) doSearchAlbums(query string) { }) if a.searchGrid == nil { a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) - a.searchGrid.OnPlay = a.onPlayAlbum - a.searchGrid.OnShowItemPage = a.onShowAlbumPage - a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage + a.contr.ConnectAlbumGridActions(a.searchGrid) } else { a.searchGrid.Reset(widgets.NewGridViewAlbumIterator(iter)) } @@ -328,18 +319,6 @@ func (a *FavoritesPage) onShowFavoriteSongs() { } } -func (a *FavoritesPage) onPlayAlbum(albumID string) { - go a.pm.PlayAlbum(albumID, 0) -} - -func (a *FavoritesPage) onShowAlbumPage(albumID string) { - a.contr.NavigateTo(controller.AlbumRoute(albumID)) -} - -func (a *FavoritesPage) onShowArtistPage(artistID string) { - a.contr.NavigateTo(controller.ArtistRoute(artistID)) -} - func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer { a.ExtendBaseWidget(a) return widget.NewSimpleRenderer(a.container) diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 70d2730..9f36836 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -53,9 +53,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs) iter := g.lm.GenreIter(g.genre) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) - g.grid.OnPlay = g.onPlayAlbum - g.grid.OnShowSecondaryPage = g.onShowArtistPage - g.grid.OnShowItemPage = g.onShowAlbumPage + g.contr.ConnectAlbumGridActions(g.grid) g.searcher = widgets.NewSearcher() g.searcher.OnSearched = g.OnSearched g.createContainer(false) @@ -146,18 +144,6 @@ func (g *GenrePage) SearchWidget() fyne.Focusable { return g.searcher.Entry } -func (a *GenrePage) onPlayAlbum(albumID string) { - go a.pm.PlayAlbum(albumID, 0) -} - -func (a *GenrePage) onShowArtistPage(artistID string) { - a.contr.NavigateTo(controller.ArtistRoute(artistID)) -} - -func (a *GenrePage) onShowAlbumPage(albumID string) { - a.contr.NavigateTo(controller.AlbumRoute(albumID)) -} - func (g *GenrePage) OnSearched(query string) { g.searchText = query if query == "" { @@ -177,9 +163,7 @@ func (g *GenrePage) doSearch(query string) { }) if g.searchGrid == nil { g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) - g.searchGrid.OnPlay = g.onPlayAlbum - g.searchGrid.OnShowItemPage = g.onShowAlbumPage - g.searchGrid.OnShowSecondaryPage = g.onShowArtistPage + g.contr.ConnectAlbumGridActions(g.searchGrid) } else { g.searchGrid.Reset(widgets.NewGridViewAlbumIterator(iter)) } diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index de198bc..26178b2 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -94,10 +94,23 @@ func (a *PlaylistsPage) createListView() { func (a *PlaylistsPage) createGridView(playlists []*subsonic.Playlist) { model := createPlaylistGridViewModel(playlists) a.gridView = widgets.NewFixedGridView(model, a.contr.App.ImageManager) - a.gridView.OnPlay = func(id string) { - a.contr.App.PlaybackManager.PlayPlaylist(id, 0) + a.gridView.OnPlay = func(id string, shuffle bool) { + go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle) + } + a.gridView.OnAddToQueue = func(id string) { + go a.contr.App.PlaybackManager.LoadPlaylist(id, true, false) } a.gridView.OnShowItemPage = a.showPlaylistPage + a.gridView.OnAddToPlaylist = func(id string) { + go func() { + pl, err := a.contr.App.ServerManager.Server.GetPlaylist(id) + if err != nil { + log.Printf("error loading playlist: %s", err.Error()) + return + } + a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Entry)) + }() + } } func (a *PlaylistsPage) showListView() { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index f191a1d..95c1ea9 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -7,6 +7,7 @@ import ( "strconv" "supersonic/backend" "supersonic/player" + "supersonic/sharedutil" "supersonic/ui/dialogs" "supersonic/ui/util" "supersonic/ui/widgets" @@ -125,6 +126,29 @@ func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { } } +func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { + grid.OnAddToQueue = func(albumID string) { + m.App.PlaybackManager.LoadAlbum(albumID, true, false) + } + grid.OnPlay = func(albumID string, shuffle bool) { + m.App.PlaybackManager.PlayAlbum(albumID, 0, shuffle) + } + grid.OnShowItemPage = func(albumID string) { + m.NavigateTo(AlbumRoute(albumID)) + } + grid.OnShowSecondaryPage = func(artistID string) { + m.NavigateTo(ArtistRoute(artistID)) + } + grid.OnAddToPlaylist = func(albumID string) { + album, err := m.App.ServerManager.Server.GetAlbum(albumID) + if err != nil { + log.Printf("error loading album: %s", err.Error()) + return + } + m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Song)) + } +} + func (m *Controller) PromptForFirstServer() { d := dialogs.NewAddEditServerDialog("Connect to Server", nil) pop := widget.NewModalPopUp(d, m.MainWindow.Canvas()) diff --git a/ui/widgets/gridview.go b/ui/widgets/gridview.go index 3c4969a..8990cd8 100644 --- a/ui/widgets/gridview.go +++ b/ui/widgets/gridview.go @@ -58,14 +58,16 @@ type GridViewState struct { items []GridViewItemModel itemsMutex sync.RWMutex iter GridViewIterator + imageFetcher ImageFetcher highestShown int fetching bool done bool - imageFetcher ImageFetcher - OnPlay func(string) - OnShowItemPage func(string) - OnShowSecondaryPage func(string) + OnPlay func(id string, shuffle bool) + OnAddToQueue func(id string) + OnAddToPlaylist func(id string) + OnShowItemPage func(id string) + OnShowSecondaryPage func(id string) scrollPos float32 } @@ -152,9 +154,14 @@ func (g *GridView) createGridWrapList() { // create func func() fyne.CanvasObject { card := NewGridViewItem() - card.OnPlay = func() { + card.OnPlay = func(shuffle bool) { if g.OnPlay != nil { - g.OnPlay(card.ItemID()) + g.OnPlay(card.ItemID(), shuffle) + } + } + card.OnAddToQueue = func() { + if g.OnAddToQueue != nil { + g.OnAddToQueue(card.ItemID()) } } card.OnShowSecondaryPage = func() { @@ -167,6 +174,11 @@ func (g *GridView) createGridWrapList() { g.OnShowItemPage(card.ItemID()) } } + card.OnAddToPlaylist = func() { + if g.OnAddToPlaylist != nil { + g.OnAddToPlaylist(card.ItemID()) + } + } return card }, // update func diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index 232cfdc..800f856 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -18,14 +18,16 @@ var _ fyne.Widget = (*GridViewItem)(nil) var _ fyne.Widget = (*coverImage)(nil) var _ fyne.Tappable = (*coverImage)(nil) +var _ fyne.SecondaryTappable = (*coverImage)(nil) type coverImage struct { widget.BaseWidget - Im *canvas.Image - playbtn *canvas.Image - OnDoubleTapped func() - OnTapped func() + Im *canvas.Image + playbtn *canvas.Image + OnPlay func() + OnShowPage func() + OnShowContextMenu func(fyne.Position) } func newCoverImage() *coverImage { @@ -51,13 +53,19 @@ func (c *coverImage) Cursor() desktop.Cursor { func (c *coverImage) Tapped(e *fyne.PointEvent) { if isInside(c.center(), c.playbtn.Size().Height/2, e.Position) { - if c.OnDoubleTapped != nil { - c.OnDoubleTapped() + if c.OnPlay != nil { + c.OnPlay() } return } - if c.OnTapped != nil { - c.OnTapped() + if c.OnShowPage != nil { + c.OnShowPage() + } +} + +func (c *coverImage) TappedSecondary(e *fyne.PointEvent) { + if c.OnShowContextMenu != nil { + c.OnShowContextMenu(e.AbsolutePosition) } } @@ -116,6 +124,7 @@ type GridViewItem struct { secondaryID string primaryText *CustomHyperlink secondaryText *CustomHyperlink + menu *widget.PopUpMenu container *fyne.Container // updated by GridView @@ -125,7 +134,9 @@ type GridViewItem struct { PrevID string ImgLoadCancel context.CancelFunc - OnPlay func() + OnPlay func(shuffle bool) + OnAddToQueue func() + OnAddToPlaylist func() OnShowItemPage func() OnShowSecondaryPage func() } @@ -137,17 +148,14 @@ func NewGridViewItem() *GridViewItem { Cover: newCoverImage(), } g.ExtendBaseWidget(g) - g.Cover.OnDoubleTapped = func() { - if g.OnPlay != nil { - g.OnPlay() - } - } + g.Cover.OnPlay = func() { g.onPlay(false) } + g.Cover.OnShowContextMenu = g.showContextMenu showItemFn := func() { if g.OnShowItemPage != nil { g.OnShowItemPage() } } - g.Cover.OnTapped = showItemFn + g.Cover.OnShowPage = showItemFn g.primaryText.OnTapped = showItemFn g.secondaryText.OnTapped = func() { if g.OnShowSecondaryPage != nil { @@ -166,6 +174,18 @@ func (g *GridViewItem) createContainer() { g.container = container.New(pad, c) } +func (g *GridViewItem) showContextMenu(pos fyne.Position) { + if g.menu == nil { + g.menu = widget.NewPopUpMenu(fyne.NewMenu("", + fyne.NewMenuItem("Play", func() { g.onPlay(false) }), + fyne.NewMenuItem("Shuffle", func() { g.onPlay(true) }), + fyne.NewMenuItem("Add to queue", g.onAddToQueue), + fyne.NewMenuItem("Add to playlist...", g.onAddToPlaylist)), + fyne.CurrentApp().Driver().CanvasForObject(g)) + } + g.menu.ShowAtPosition(pos) +} + func (g *GridViewItem) Update(model GridViewItemModel) { g.itemID = model.ID g.secondaryID = model.SecondaryID @@ -187,3 +207,21 @@ func (g *GridViewItem) SecondaryID() string { func (g *GridViewItem) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(g.container) } + +func (g *GridViewItem) onPlay(shuffle bool) { + if g.OnPlay != nil { + g.OnPlay(shuffle) + } +} + +func (g *GridViewItem) onAddToQueue() { + if g.OnAddToQueue != nil { + g.OnAddToQueue() + } +} + +func (g *GridViewItem) onAddToPlaylist() { + if g.OnAddToPlaylist != nil { + g.OnAddToPlaylist() + } +}