From 60b7b93ee27f22781dbcddc8bd429d0ede8514fb Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Mar 2023 17:17:02 -0700 Subject: [PATCH] add edit/delete playlist functionality --- ui/browsing/browsingpane.go | 4 ++ ui/browsing/playlistpage.go | 17 +++++-- ui/controller/controller.go | 58 ++++++++++++++++++++++-- ui/dialogs/editplaylistdialog.go | 77 ++++++++++++++++++++++++++++++++ ui/mainwindow.go | 4 ++ 5 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 ui/dialogs/editplaylistdialog.go diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index d287d54..064449a 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -209,6 +209,10 @@ func (b *BrowsingPane) Reload() { } } +func (b *BrowsingPane) CurPage() controller.Route { + return b.curPage.Route() +} + func (b *BrowsingPane) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(b.container) } diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index c5bf270..714a46a 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -123,10 +123,11 @@ func (a *PlaylistPage) onRemoveSelectedFromPlaylist() { type PlaylistPageHeader struct { widget.BaseWidget - page *PlaylistPage - - image *widgets.ImagePlaceholder + page *PlaylistPage + playlistInfo *subsonic.Playlist + image *widgets.ImagePlaceholder + editButton *widget.Button titleLabel *widget.RichText descriptionLabel *widget.Label createdAtLabel *widget.Label @@ -150,6 +151,12 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { a.ownerLabel = widget.NewLabel("") a.createdAtLabel = widget.NewLabel("") a.trackTimeLabel = widget.NewLabel("") + a.editButton = widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func() { + if a.playlistInfo != nil { + page.contr.DoEditPlaylistWorkflow(a.playlistInfo) + } + }) + a.editButton.Hidden = true playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { page.pm.LoadTracks(page.tracklist.Tracks, false, false) page.pm.PlayFromBeginning() @@ -165,7 +172,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { a.descriptionLabel, a.ownerLabel, a.trackTimeLabel), - container.NewHBox(playButton, shuffleBtn), + container.NewHBox(a.editButton, playButton, shuffleBtn), )) return a } @@ -175,6 +182,8 @@ func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer { } func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) { + a.playlistInfo = playlist + a.editButton.Hidden = playlist.Owner != a.page.sm.Server.User a.titleLabel.Segments[0].(*widget.TextSegment).Text = playlist.Name a.descriptionLabel.SetText(playlist.Comment) a.ownerLabel.SetText(a.formatPlaylistOwnerStr(playlist)) diff --git a/ui/controller/controller.go b/ui/controller/controller.go index de3990e..ee2dd0e 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -3,6 +3,7 @@ package controller import ( "image" "log" + "strconv" "supersonic/backend" "supersonic/ui/dialogs" "supersonic/ui/util" @@ -12,20 +13,27 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "github.com/dweymouth/go-subsonic/subsonic" ) type NavigationHandler func(Route) +type ReloadFunc func() + +type CurPageFunc func() Route + type Controller struct { // if not nil, this popup should be hidden when escape is pressed EscapablePopUp *widget.PopUp - AppVersion string - MainWindow fyne.Window - App *backend.App - NavHandler NavigationHandler + AppVersion string + MainWindow fyne.Window + App *backend.App + NavHandler NavigationHandler + CurPageFunc CurPageFunc + ReloadFunc ReloadFunc } func (m *Controller) NavigateTo(route Route) { @@ -147,6 +155,48 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { pop.Show() } +func (m *Controller) DoEditPlaylistWorkflow(playlist *subsonic.Playlist) { + dlg := dialogs.NewEditPlaylistDialog(playlist) + pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas()) + m.ClosePopUpOnEscape(pop) + dlg.OnCanceled = pop.Hide + dlg.OnDeletePlaylist = func() { + pop.Hide() + dialog.ShowCustomConfirm("Confirm Delete Playlist", "OK", "Cancel", layout.NewSpacer(), /*custom content*/ + func(ok bool) { + if !ok { + pop.Show() + } else { + go func() { + if err := m.App.ServerManager.Server.DeletePlaylist(playlist.ID); err != nil { + log.Printf("error deleting playlist: %s", err.Error()) + } else if rte := m.CurPageFunc(); rte.Page == Playlist && rte.Arg == playlist.ID { + // navigate to playlists page if user is still on the page of the deleted playlist + m.NavigateTo(PlaylistsRoute()) + } + }() + } + }, m.MainWindow) + } + dlg.OnUpdateMetadata = func() { + pop.Hide() + go func() { + err := m.App.ServerManager.Server.UpdatePlaylist(playlist.ID, map[string]string{ + "name": dlg.Name, + "comment": dlg.Description, + "public": strconv.FormatBool(dlg.IsPublic), + }) + if err != nil { + log.Printf("error updating playlist: %s", err.Error()) + } else if rte := m.CurPageFunc(); rte.Page == Playlist && rte.Arg == playlist.ID { + // if user is on playlist page, reload to get the updates + m.ReloadFunc() + } + }() + } + pop.Show() +} + func (c *Controller) DoConnectToServerWorkflow(server *backend.ServerConfig) { pass, err := c.App.ServerManager.GetServerPassword(server) if err != nil { diff --git a/ui/dialogs/editplaylistdialog.go b/ui/dialogs/editplaylistdialog.go new file mode 100644 index 0000000..20e09a9 --- /dev/null +++ b/ui/dialogs/editplaylistdialog.go @@ -0,0 +1,77 @@ +package dialogs + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + "github.com/dweymouth/go-subsonic/subsonic" +) + +type EditPlaylistDialog struct { + widget.BaseWidget + + OnCanceled func() + OnDeletePlaylist func() + OnUpdateMetadata func() + + IsPublic bool + Name string + Description string + + container *fyne.Container +} + +func NewEditPlaylistDialog(playlist *subsonic.Playlist) *EditPlaylistDialog { + e := &EditPlaylistDialog{ + IsPublic: playlist.Public, + Name: playlist.Name, + Description: playlist.Comment, + } + e.ExtendBaseWidget(e) + + isPublicCheck := widget.NewCheckWithData("Public", binding.BindBool(&e.IsPublic)) + nameEntry := widget.NewEntryWithData(binding.BindString(&e.Name)) + descriptionEntry := widget.NewEntryWithData(binding.BindString(&e.Description)) + deleteBtn := widget.NewButton("Delete Playlist", func() { + if e.OnDeletePlaylist != nil { + e.OnDeletePlaylist() + } + }) + submitBtn := widget.NewButton("OK", func() { + if e.OnUpdateMetadata != nil { + e.OnUpdateMetadata() + } + }) + cancelBtn := widget.NewButton("Cancel", func() { + if e.OnCanceled != nil { + e.OnCanceled() + } + }) + + e.container = container.NewVBox( + container.NewHBox(layout.NewSpacer(), widget.NewLabel("Edit Playlist"), layout.NewSpacer()), + container.New(layout.NewFormLayout(), + widget.NewLabel("Name"), + nameEntry, + widget.NewLabel("Description"), + descriptionEntry, + ), + container.NewHBox(isPublicCheck, layout.NewSpacer(), deleteBtn), + widget.NewSeparator(), + container.NewHBox( + layout.NewSpacer(), + cancelBtn, submitBtn), + ) + + return e +} + +func (e *EditPlaylistDialog) MinSize() fyne.Size { + return fyne.NewSize(300, e.BaseWidget.MinSize().Height) +} + +func (e *EditPlaylistDialog) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(e.container) +} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 42cb1b7..1683c56 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -60,7 +60,11 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap App: app, } m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane) + // inject controller dependencies m.Controller.NavHandler = m.Router.NavigateTo + m.Controller.ReloadFunc = m.BrowsingPane.Reload + m.Controller.CurPageFunc = m.BrowsingPane.CurPage + m.BottomPanel = NewBottomPanel(app.Player, m.Router.NavigateTo) m.BottomPanel.SetPlaybackManager(app.PlaybackManager) m.BottomPanel.ImageManager = app.ImageManager