chore: Improve 'Add to playlists' dialog
This commit is contained in:
@@ -179,4 +179,5 @@ type SearchResult struct {
|
|||||||
|
|
||||||
// Unset for ContentTypes Artist, Playlist, and Genre
|
// Unset for ContentTypes Artist, Playlist, and Genre
|
||||||
ArtistName string
|
ArtistName string
|
||||||
|
Query string
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-39
@@ -322,49 +322,33 @@ func (m *Controller) PromptForFirstServer() {
|
|||||||
pop.Show()
|
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
|
// Depending on the results of that dialog, potentially create a new playlist
|
||||||
// Add tracks to the user-specified playlist
|
// Add tracks to the user-specified playlist
|
||||||
func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
||||||
go func() {
|
sp := dialogs.NewSelectPlaylistDialog(m.App.ServerManager.Server, m.App.ImageManager, m.App.ServerManager.LoggedInUser)
|
||||||
pls, err := m.App.ServerManager.Server.GetPlaylists()
|
pop := widget.NewModalPopUp(sp.SearchDialog, m.MainWindow.Canvas())
|
||||||
pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool {
|
sp.SetOnDismiss(func() {
|
||||||
return pl.Owner == m.App.ServerManager.LoggedInUser
|
pop.Hide()
|
||||||
})
|
m.doModalClosed()
|
||||||
if err != nil {
|
})
|
||||||
// TODO: surface this error to user
|
sp.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string, query string) {
|
||||||
log.Printf("error getting user-owned playlists: %s", err.Error())
|
pop.Hide()
|
||||||
return
|
if id == "" {
|
||||||
|
go m.App.ServerManager.Server.CreatePlaylist(query, trackIDs)
|
||||||
|
} else {
|
||||||
|
m.App.Config.Application.DefaultPlaylistID = id
|
||||||
|
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedIdx := -1
|
})
|
||||||
plNames := make([]string, 0, len(pls))
|
m.ClosePopUpOnEscape(pop)
|
||||||
for i, pl := range pls {
|
m.haveModal = true
|
||||||
plNames = append(plNames, pl.Name)
|
min := sp.MinSize()
|
||||||
if defId := m.App.Config.Application.DefaultPlaylistID; defId != "" && pl.ID == defId {
|
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, m.MainWindow.Canvas().Size().Height*0.7))
|
||||||
selectedIdx = i
|
pop.Resize(fyne.NewSize(min.Width, height))
|
||||||
}
|
pop.Show()
|
||||||
}
|
m.MainWindow.Canvas().Focus(sp.GetSearchEntry())
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
playlist := pls[playlistChoice]
|
|
||||||
m.App.Config.Application.DefaultPlaylistID = playlist.ID
|
|
||||||
go m.App.ServerManager.Server.AddPlaylistTracks(
|
|
||||||
playlist.ID, trackIDs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.haveModal = true
|
|
||||||
pop.Show()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
|
func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
|
||||||
@@ -622,7 +606,7 @@ func (c *Controller) ShowQuickSearch() {
|
|||||||
pop.Hide()
|
pop.Hide()
|
||||||
c.doModalClosed()
|
c.doModalClosed()
|
||||||
})
|
})
|
||||||
qs.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string) {
|
qs.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string, query string) {
|
||||||
pop.Hide()
|
pop.Hide()
|
||||||
c.doModalClosed()
|
c.doModalClosed()
|
||||||
switch contentType {
|
switch contentType {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (q *QuickSearch) SetOnDismiss(onDismiss func()) {
|
|||||||
q.SearchDialog.OnDismiss = onDismiss
|
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
|
q.SearchDialog.OnNavigateTo = onNavigateTo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type SearchDialog struct {
|
|||||||
content *fyne.Container
|
content *fyne.Container
|
||||||
|
|
||||||
OnDismiss func()
|
OnDismiss func()
|
||||||
OnNavigateTo func(mediaprovider.ContentType, string)
|
OnNavigateTo func(mediaprovider.ContentType, string, string)
|
||||||
OnSearched func(string) []*mediaprovider.SearchResult
|
OnSearched func(string) []*mediaprovider.SearchResult
|
||||||
OnUpdateSearchResults func(*searchResult, *mediaprovider.SearchResult)
|
OnUpdateSearchResults func(*searchResult, *mediaprovider.SearchResult)
|
||||||
}
|
}
|
||||||
@@ -109,8 +109,9 @@ func (sd *SearchDialog) onSelected(idx int) {
|
|||||||
}
|
}
|
||||||
id := sd.searchResults[idx].ID
|
id := sd.searchResults[idx].ID
|
||||||
typ := sd.searchResults[idx].Type
|
typ := sd.searchResults[idx].Type
|
||||||
|
query := sd.searchResults[idx].Query
|
||||||
sd.resultsMutex.RUnlock()
|
sd.resultsMutex.RUnlock()
|
||||||
sd.OnNavigateTo(typ, id)
|
sd.OnNavigateTo(typ, id, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SearchDialog) moveSelectionDown() {
|
func (sd *SearchDialog) moveSelectionDown() {
|
||||||
@@ -164,7 +165,7 @@ func (sd *SearchDialog) update(sr *searchResult, result *mediaprovider.SearchRes
|
|||||||
if result == nil {
|
if result == nil {
|
||||||
return
|
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
|
return // nothing to do
|
||||||
}
|
}
|
||||||
sr.id = result.ID
|
sr.id = result.ID
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package dialogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "fmt"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/deluan/sanitize"
|
||||||
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelectPlaylist struct {
|
||||||
|
SearchDialog *SearchDialog
|
||||||
|
mp mediaprovider.MediaProvider
|
||||||
|
loggedInUser string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetcher, loggedInUser string) *SelectPlaylist {
|
||||||
|
|
||||||
|
sp := &SelectPlaylist{
|
||||||
|
mp: mp,
|
||||||
|
loggedInUser: loggedInUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
sd := NewSearchDialog(
|
||||||
|
im,
|
||||||
|
"Select playlist",
|
||||||
|
sp.onSearched,
|
||||||
|
sp.onUpdateSearchResult,
|
||||||
|
)
|
||||||
|
sp.SearchDialog = sd
|
||||||
|
return sp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) onSearched(query string) []*mediaprovider.SearchResult {
|
||||||
|
var results []*mediaprovider.SearchResult
|
||||||
|
if query != "" {
|
||||||
|
var filteredPlaylists []*mediaprovider.Playlist
|
||||||
|
if playlists, err := sp.mp.GetPlaylists(); err != nil {
|
||||||
|
// TODO: surface this error to user
|
||||||
|
log.Printf("error getting playlists: %s", err.Error())
|
||||||
|
return results
|
||||||
|
} else {
|
||||||
|
filteredPlaylists = sharedutil.FilterSlice(playlists, func(playlist *mediaprovider.Playlist) bool {
|
||||||
|
return strings.Contains(
|
||||||
|
sanitize.Accents(strings.ToLower(playlist.Name)),
|
||||||
|
sanitize.Accents(strings.ToLower(query)),
|
||||||
|
) && playlist.Owner == sp.loggedInUser
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, &mediaprovider.SearchResult{
|
||||||
|
Name: fmt.Sprintf("Create new playlist: %s", query),
|
||||||
|
Type: mediaprovider.ContentTypePlaylist,
|
||||||
|
Query: query,
|
||||||
|
})
|
||||||
|
for _, playlist := range filteredPlaylists {
|
||||||
|
results = append(results, &mediaprovider.SearchResult{
|
||||||
|
Name: playlist.Name,
|
||||||
|
ID: playlist.ID,
|
||||||
|
CoverID: playlist.CoverArtID,
|
||||||
|
Type: mediaprovider.ContentTypePlaylist,
|
||||||
|
Size: playlist.TrackCount,
|
||||||
|
ArtistName: playlist.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) onUpdateSearchResult(sr *searchResult, result *mediaprovider.SearchResult) {
|
||||||
|
if result.ID == "" {
|
||||||
|
sr.secondary.Segments = []widget.RichTextSegment{}
|
||||||
|
sr.secondary.Refresh()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maybePluralize := func(s string, size int) string {
|
||||||
|
if size != 1 {
|
||||||
|
return s + "s"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
secondaryText := fmt.Sprintf("%d %s", result.Size, maybePluralize("track", result.Size))
|
||||||
|
sr.secondary.Segments = []widget.RichTextSegment{
|
||||||
|
&widget.TextSegment{
|
||||||
|
Text: result.Type.String(),
|
||||||
|
Style: widget.RichTextStyle{SizeName: theme.SizeNameCaptionText, TextStyle: fyne.TextStyle{Bold: true}, Inline: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if secondaryText != "" {
|
||||||
|
sr.secondary.Segments = append(sr.secondary.Segments,
|
||||||
|
&widget.TextSegment{
|
||||||
|
Text: " · ",
|
||||||
|
Style: widget.RichTextStyle{SizeName: theme.SizeNameCaptionText, Inline: true},
|
||||||
|
},
|
||||||
|
&widget.TextSegment{
|
||||||
|
Text: secondaryText,
|
||||||
|
Style: widget.RichTextStyle{SizeName: theme.SizeNameCaptionText, Inline: true},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sr.secondary.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) SetOnDismiss(onDismiss func()) {
|
||||||
|
sp.SearchDialog.OnDismiss = onDismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) SetOnNavigateTo(onNavigateTo func(mediaprovider.ContentType, string, string)) {
|
||||||
|
sp.SearchDialog.OnNavigateTo = onNavigateTo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) MinSize() fyne.Size {
|
||||||
|
return sp.SearchDialog.MinSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SelectPlaylist) GetSearchEntry() fyne.Focusable {
|
||||||
|
return sp.SearchDialog.SearchEntry
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user