Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Improve 'Add to playlists' dialog #382

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about adding Query to the SearchResult model, since that isn't really part of the search result per se. Instead, maybe the searchdialog can expose the query through another function call (SearchDialog.QueryText()?)

}
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
Loading