Skip to content

Commit

Permalink
Merge pull request #382 from natilou/new-add-to-playlist
Browse files Browse the repository at this point in the history
chore: Improve 'Add to playlists' dialog
  • Loading branch information
dweymouth authored May 23, 2024
2 parents ce647c2 + 073f7c7 commit 84d4208
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 66 deletions.
1 change: 1 addition & 0 deletions backend/mediaprovider/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,5 @@ type SearchResult struct {

// Unset for ContentTypes Artist, Playlist, and Genre
ArtistName string
Query string
}
81 changes: 44 additions & 37 deletions ui/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"time"

"github.com/dweymouth/supersonic/backend"
Expand Down Expand Up @@ -322,49 +323,54 @@ func (m *Controller) PromptForFirstServer() {
pop.Show()
}

// Show dialog to prompt for playlist.
// Show dialog to select playlist.
// Depending on the results of that dialog, potentially create a new playlist
// Add tracks to the user-specified playlist
func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
go func() {
pls, err := m.App.ServerManager.Server.GetPlaylists()
pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool {
return pl.Owner == m.App.ServerManager.LoggedInUser
})
if err != nil {
// TODO: surface this error to user
log.Printf("error getting user-owned playlists: %s", err.Error())
return
}

selectedIdx := -1
plNames := make([]string, 0, len(pls))
for i, pl := range pls {
plNames = append(plNames, pl.Name)
if defId := m.App.Config.Application.DefaultPlaylistID; defId != "" && pl.ID == defId {
selectedIdx = i
}
}
sp := dialogs.NewSelectPlaylistDialog(m.App.ServerManager.Server, m.App.ImageManager, m.App.ServerManager.LoggedInUser)
pop := widget.NewModalPopUp(sp.SearchDialog, m.MainWindow.Canvas())
sp.SetOnDismiss(func() {
pop.Hide()
m.doModalClosed()
})
sp.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string, query string) {
pop.Hide()
if id == "" {
go m.App.ServerManager.Server.CreatePlaylist(query, trackIDs)
} else {
m.App.Config.Application.DefaultPlaylistID = id
if sp.SkipDuplicates {
var filterTrackIDs []string
go func() {
if selectedPlaylist, err := m.App.ServerManager.Server.GetPlaylist(id); err != nil {
log.Printf("error getting playlist: %s", err.Error())
} else {
var trackIDsInPaylist []string

for _, track := range selectedPlaylist.Tracks {
trackIDsInPaylist = append(trackIDsInPaylist, track.ID)
}
filterTrackIDs = sharedutil.FilterSlice(trackIDs, func(trackID string) bool {
return !slices.Contains(trackIDsInPaylist, trackID)
})

dlg := dialogs.NewAddToPlaylistDialog("Add to Playlist", plNames, selectedIdx)
pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas())
m.ClosePopUpOnEscape(pop)
dlg.OnCanceled = pop.Hide
dlg.OnSubmit = func(playlistChoice int, newPlaylistName string) {
pop.Hide()
m.doModalClosed()
if playlistChoice < 0 {
go m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs)
}
m.App.ServerManager.Server.AddPlaylistTracks(id, filterTrackIDs)
}()
} else {
playlist := pls[playlistChoice]
m.App.Config.Application.DefaultPlaylistID = playlist.ID
go m.App.ServerManager.Server.AddPlaylistTracks(
playlist.ID, trackIDs)
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
}
}
m.haveModal = true
pop.Show()
}()

})
m.ClosePopUpOnEscape(pop)
m.haveModal = true
min := sp.MinSize()
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, m.MainWindow.Canvas().Size().Height*0.7))
sp.SearchDialog.Show()
pop.Resize(fyne.NewSize(min.Width, height))
pop.Show()
m.MainWindow.Canvas().Focus(sp.GetSearchEntry())
}

func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
Expand Down Expand Up @@ -622,7 +628,7 @@ func (c *Controller) ShowQuickSearch() {
pop.Hide()
c.doModalClosed()
})
qs.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string) {
qs.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string, query string) {
pop.Hide()
c.doModalClosed()
switch contentType {
Expand All @@ -642,6 +648,7 @@ func (c *Controller) ShowQuickSearch() {
c.haveModal = true
min := qs.MinSize()
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, c.MainWindow.Canvas().Size().Height*0.7))
qs.SearchDialog.Show()
pop.Resize(fyne.NewSize(min.Width, height))
pop.Show()
c.MainWindow.Canvas().Focus(qs.GetSearchEntry())
Expand Down
3 changes: 2 additions & 1 deletion ui/dialogs/quicksearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewQuickSearch(mp mediaprovider.MediaProvider, im util.ImageFetcher) *Quick
"Quick Search",
q.onSearched,
q.onUpdateSearchResult,
nil,
)
q.SearchDialog = sd
return q
Expand Down Expand Up @@ -95,7 +96,7 @@ func (q *QuickSearch) SetOnDismiss(onDismiss func()) {
q.SearchDialog.OnDismiss = onDismiss
}

func (q *QuickSearch) SetOnNavigateTo(onNavigateTo func(mediaprovider.ContentType, string)) {
func (q *QuickSearch) SetOnNavigateTo(onNavigateTo func(mediaprovider.ContentType, string, string)) {
q.SearchDialog.OnNavigateTo = onNavigateTo
}

Expand Down
99 changes: 71 additions & 28 deletions ui/dialogs/searchdialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,24 @@ type SearchDialog struct {
list *widget.List
selectedIndex int

content *fyne.Container
placeholderTitle string
content *fyne.Container

OnDismiss func()
OnNavigateTo func(mediaprovider.ContentType, string)
OnNavigateTo func(mediaprovider.ContentType, string, string)
OnSearched func(string) []*mediaprovider.SearchResult
OnUpdateSearchResults func(*searchResult, *mediaprovider.SearchResult)
OnInit func() ([]*mediaprovider.SearchResult, *widget.Check)
}

func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched func(string) []*mediaprovider.SearchResult, onUpdateSearchResult func(*searchResult, *mediaprovider.SearchResult)) *SearchDialog {
func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched func(string) []*mediaprovider.SearchResult, onUpdateSearchResult func(*searchResult, *mediaprovider.SearchResult), onInit func() ([]*mediaprovider.SearchResult, *widget.Check)) *SearchDialog {
sd := &SearchDialog{
imgSource: im,
loadingDots: widgets.NewLoadingDots(),
OnSearched: onSearched,
OnUpdateSearchResults: onUpdateSearchResult,
OnInit: onInit,
placeholderTitle: placeholderTitle,
}
sd.ExtendBaseWidget(sd)

Expand Down Expand Up @@ -78,20 +82,14 @@ func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched f
sd.update(sr, result)
},
)

dismissBtn := widget.NewButton("Close", sd.onDismiss)
title := widget.NewRichText(&widget.TextSegment{Text: placeholderTitle, Style: util.BoldRichTextStyle})
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
sd.content = container.NewStack(
container.NewBorder(
container.NewVBox(title, se),
container.NewVBox(widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), dismissBtn)),
nil, nil, sd.list),
container.NewCenter(sd.loadingDots),
)
return sd
}

func (sd *SearchDialog) Show() {
sd.onInit()
sd.BaseWidget.Show()
}

func (sd *SearchDialog) onDismiss() {
if sd.OnDismiss != nil {
sd.OnDismiss()
Expand All @@ -109,8 +107,9 @@ func (sd *SearchDialog) onSelected(idx int) {
}
id := sd.searchResults[idx].ID
typ := sd.searchResults[idx].Type
query := sd.searchResults[idx].Query
sd.resultsMutex.RUnlock()
sd.OnNavigateTo(typ, id)
sd.OnNavigateTo(typ, id, query)
}

func (sd *SearchDialog) moveSelectionDown() {
Expand All @@ -131,18 +130,7 @@ func (sd *SearchDialog) moveSelectionUp() {
sd.list.Select(sd.selectedIndex)
}

func (sd *SearchDialog) onSearched(query string) {
sd.loadingDots.Start()
var results []*mediaprovider.SearchResult
if query != "" {
res := sd.OnSearched(query)
if len(res) == 0 {
log.Println("No results matched the query.")
} else {
results = res
}
}
sd.loadingDots.Stop()
func (sd *SearchDialog) setResults(results []*mediaprovider.SearchResult) {
sd.resultsMutex.Lock()
sd.searchResults = results
sd.resultsMutex.Unlock()
Expand All @@ -152,6 +140,61 @@ func (sd *SearchDialog) onSearched(query string) {
sd.list.Select(0)
}

func (sd *SearchDialog) SetContent(checkBox *widget.Check) {
dismissBtn := widget.NewButton("Close", sd.onDismiss)
title := widget.NewRichText(&widget.TextSegment{Text: sd.placeholderTitle, Style: util.BoldRichTextStyle})
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
se := sd.SearchEntry.(fyne.CanvasObject)
if checkBox != nil {
sd.content = container.NewStack(
container.NewBorder(
container.NewVBox(title, se),
container.NewVBox(widget.NewSeparator(), container.NewHBox(checkBox, layout.NewSpacer(), dismissBtn)),
nil, nil, sd.list),
container.NewCenter(sd.loadingDots),
)
} else {
sd.content = container.NewStack(
container.NewBorder(
container.NewVBox(title, se),
container.NewVBox(widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), dismissBtn)),
nil, nil, sd.list),
container.NewCenter(sd.loadingDots),
)
}
}

func (sd *SearchDialog) onInit() {
if sd.OnInit == nil {
sd.SetContent(nil)
return
}
sd.loadingDots.Start()
var results []*mediaprovider.SearchResult
res, checkBox := sd.OnInit()
if len(res) == 0 {
log.Println("No results")
} else {
results = res
}
sd.SetContent(checkBox)
sd.loadingDots.Stop()
sd.setResults(results)
}

func (sd *SearchDialog) onSearched(query string) {
sd.loadingDots.Start()
var results []*mediaprovider.SearchResult
res := sd.OnSearched(query)
if len(res) == 0 {
log.Println("No results matched the query.")
} else {
results = res
}
sd.loadingDots.Stop()
sd.setResults(results)
}

func (sd *SearchDialog) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(sd.content)
}
Expand All @@ -164,7 +207,7 @@ func (sd *SearchDialog) update(sr *searchResult, result *mediaprovider.SearchRes
if result == nil {
return
}
if sr.contentType == result.Type && sr.id == result.ID {
if sr.contentType == result.Type && sr.id == result.ID && result.ID != "" {
return // nothing to do
}
sr.id = result.ID
Expand Down
Loading

0 comments on commit 84d4208

Please sign in to comment.