Merge pull request #336 from adamantike/feat/add-share-option

feat: Add Share option for artists, albums, and tracks
This commit is contained in:
Drew Weymouth
2024-03-03 16:39:18 -08:00
committed by GitHub
21 changed files with 226 additions and 18 deletions
+6
View File
@@ -3,6 +3,7 @@ package mediaprovider
import (
"image"
"io"
"net/url"
"strings"
)
@@ -148,6 +149,11 @@ type SupportsRating interface {
SetRating(params RatingFavoriteParameters, rating int) error
}
type SupportsSharing interface {
CreateShareURL(id string) (*url.URL, error)
CanShareArtists() bool
}
type JukeboxProvider interface {
JukeboxStart() error
JukeboxStop() error
@@ -5,6 +5,7 @@ import (
"image"
"io"
"math"
"net/url"
"strconv"
"strings"
"sync"
@@ -315,6 +316,26 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar
return err
}
func (s *subsonicMediaProvider) CreateShareURL(id string) (*url.URL, error) {
share, err := s.client.CreateShare(id, nil)
if err != nil {
return nil, err
}
shareUrl, err := url.Parse(share.Url)
if err != nil {
return nil, err
}
return shareUrl, nil
}
func (s *subsonicMediaProvider) CanShareArtists() bool {
// TODO: Change to true when we decide to allow sharing artists, in case an OpenSubsonic extension
// is approved to share artists in addition to albums and tracks.
return false
}
func (s *subsonicMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
return s.client.Download(trackID)
}
+1 -1
View File
@@ -8,7 +8,7 @@ require (
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
github.com/dweymouth/go-subsonic v0.0.0-20231217175944-9b48c9ffc002
github.com/dweymouth/go-subsonic v0.0.0-20240225165422-3617754d751b
github.com/fsnotify/fsnotify v1.6.0
github.com/godbus/dbus/v5 v5.1.0
github.com/google/uuid v1.3.0
+2 -2
View File
@@ -75,8 +75,8 @@ github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc h1:wJy4U12Ys
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc/go.mod h1:BMwS4vdjEYf1gmjPGSKCzWP/I6YlI6fkefJ9nsjBjaU=
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY=
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0=
github.com/dweymouth/go-subsonic v0.0.0-20231217175944-9b48c9ffc002 h1:DhWQZJObkCUSFmHu/eEzHRqy0R33DcR266nPAvZJE34=
github.com/dweymouth/go-subsonic v0.0.0-20231217175944-9b48c9ffc002/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo=
github.com/dweymouth/go-subsonic v0.0.0-20240225165422-3617754d751b h1:LGUWXRcndcWwBN0HTFsCUlIjr8aakRsi9FlUX1CPSs8=
github.com/dweymouth/go-subsonic v0.0.0-20240225165422-3617754d751b/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+5
View File
@@ -95,6 +95,11 @@ var ResShuffleSvg = &fyne.StaticResource{
StaticContent: []byte(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<svg width=\"800px\" height=\"800px\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <path fill=\"none\" d=\"M0 0h24v24H0z\"/>\n <path d=\"M18 17.883V16l5 3-5 3v-2.09a9 9 0 0 1-6.997-5.365L11 14.54l-.003.006A9 9 0 0 1 2.725 20H2v-2h.725a7 7 0 0 0 6.434-4.243L9.912 12l-.753-1.757A7 7 0 0 0 2.725 6H2V4h.725a9 9 0 0 1 8.272 5.455L11 9.46l.003-.006A9 9 0 0 1 18 4.09V2l5 3-5 3V6.117a7 7 0 0 0-5.159 4.126L12.088 12l.753 1.757A7 7 0 0 0 18 17.883z\"/>\n </g>\n</svg>"),
}
var ResShareSvg = &fyne.StaticResource{
StaticName: "share.svg",
StaticContent: []byte(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<svg width=\"800px\" height=\"800px\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <path d=\"M13.1202 17.0228L8.92129 14.7324C8.19135 15.5125 7.15261 16 6 16C3.79086 16 2 14.2091 2 12C2 9.79086 3.79086 8 6 8C7.15255 8 8.19125 8.48746 8.92118 9.26746L13.1202 6.97713C13.0417 6.66441 13 6.33707 13 6C13 3.79086 14.7909 2 17 2C19.2091 2 21 3.79086 21 6C21 8.20914 19.2091 10 17 10C15.8474 10 14.8087 9.51251 14.0787 8.73246L9.87977 11.0228C9.9583 11.3355 10 11.6629 10 12C10 12.3371 9.95831 12.6644 9.87981 12.9771L14.0788 15.2675C14.8087 14.4875 15.8474 14 17 14C19.2091 14 21 15.7909 21 18C21 20.2091 19.2091 22 17 22C14.7909 22 13 20.2091 13 18C13 17.6629 13.0417 17.3355 13.1202 17.0228ZM6 14C7.10457 14 8 13.1046 8 12C8 10.8954 7.10457 10 6 10C4.89543 10 4 10.8954 4 12C4 13.1046 4.89543 14 6 14ZM17 8C18.1046 8 19 7.10457 19 6C19 4.89543 18.1046 4 17 4C15.8954 4 15 4.89543 15 6C15 7.10457 15.8954 8 17 8ZM17 20C18.1046 20 19 19.1046 19 18C19 16.8954 18.1046 16 17 16C15.8954 16 15 16.8954 15 18C15 19.1046 15.8954 20 17 20Z\" />\n </g>\n</svg>\n"),
}
var ResUpdownarrowSvg = &fyne.StaticResource{
StaticName: "updownarrow.svg",
StaticContent: []byte(
+1
View File
@@ -18,6 +18,7 @@ fyne bundle -append -prefix Res icons/publicdomain/filter.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/repeat.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/repeatone.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/shuffle.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/share.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/updownarrow.svg >> bundled.go
fyne bundle -append -prefix Res themes/default.toml >> bundled.go
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M13.1202 17.0228L8.92129 14.7324C8.19135 15.5125 7.15261 16 6 16C3.79086 16 2 14.2091 2 12C2 9.79086 3.79086 8 6 8C7.15255 8 8.19125 8.48746 8.92118 9.26746L13.1202 6.97713C13.0417 6.66441 13 6.33707 13 6C13 3.79086 14.7909 2 17 2C19.2091 2 21 3.79086 21 6C21 8.20914 19.2091 10 17 10C15.8474 10 14.8087 9.51251 14.0787 8.73246L9.87977 11.0228C9.9583 11.3355 10 11.6629 10 12C10 12.3371 9.95831 12.6644 9.87981 12.9771L14.0788 15.2675C14.8087 14.4875 15.8474 14 17 14C19.2091 14 21 15.7909 21 18C21 20.2091 19.2091 22 17 22C14.7909 22 13 20.2091 13 18C13 17.6629 13.0417 17.3355 13.1202 17.0228ZM6 14C7.10457 14 8 13.1046 8 12C8 10.8954 7.10457 10 6 10C4.89543 10 4 10.8954 4 12C4 13.1046 4.89543 14 6 14ZM17 8C18.1046 8 19 7.10457 19 6C19 4.89543 18.1046 4 17 4C15.8954 4 15 4.89543 15 6C15 7.10457 15.8954 8 17 8ZM17 20C18.1046 20 19 19.1046 19 18C19 16.8954 18.1046 16 17 16C15.8954 16 15 16.8954 15 18C15 19.1046 15.8954 20 17 20Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+10 -1
View File
@@ -94,7 +94,9 @@ func newAlbumPage(
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
a.tracklist.SetSorting(sort)
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
a.tracklist.Options.DisableRating = !canRate
a.tracklist.Options.DisableSharing = !canShare
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
a.cfg.TracklistColumns = cols
}
@@ -183,6 +185,7 @@ type AlbumPageHeader struct {
artistLabelSpace *util.HSpace // TODO: remove when no longer needed
genreLabel *widgets.MultiHyperlink
miscLabel *widget.Label
shareMenuItem *fyne.MenuItem
toggleFavButton *widgets.FavoriteButton
@@ -246,9 +249,15 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image())
})
info.Icon = theme.InfoIcon()
menu := fyne.NewMenu("", queue, playlist, download, info)
a.shareMenuItem = fyne.NewMenuItem("Share...", func() {
a.page.contr.ShowShareDialog(a.albumID)
})
a.shareMenuItem.Icon = myTheme.ShareIcon
menu := fyne.NewMenu("", queue, playlist, download, info, a.shareMenuItem)
pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a))
}
_, canShare := page.mp.(mediaprovider.SupportsSharing)
a.shareMenuItem.Disabled = !canShare
pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn)
pop.ShowAtPosition(fyne.NewPos(pos.X, pos.Y+menuBtn.Size().Height))
}
+32 -1
View File
@@ -237,7 +237,9 @@ func (a *ArtistPage) showTopTracks() {
}
tl.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
tl.Options.DisableRating = !canRate
tl.Options.DisableSharing = !canShare
tl.SetVisibleColumns(a.cfg.TracklistColumns)
tl.SetSorting(a.trackSort)
tl.OnVisibleColumnsChanged = func(cols []string) {
@@ -292,7 +294,9 @@ type ArtistPageHeader struct {
favoriteBtn *widgets.FavoriteButton
playBtn *widget.Button
playRadioBtn *widget.Button
menuBtn *widget.Button
container *fyne.Container
shareMenuItem *fyne.MenuItem
}
func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
@@ -318,6 +322,28 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
go a.artistPage.contr.PlayArtistDiscography(a.artistID, false /*shuffle*/)
})
a.playRadioBtn = widget.NewButtonWithIcon("Play Artist Radio", myTheme.ShuffleIcon, a.artistPage.playArtistRadio)
// TODO: Uncomment when at least one media provider supports sharing artists.
// a.shareMenuItem = fyne.NewMenuItem("Share...", func() {
// a.artistPage.contr.ShowShareDialog(a.artistID)
// })
// a.shareMenuItem.Icon = myTheme.ShareIcon
// var pop *widget.PopUpMenu
// a.menuBtn = widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil)
// a.menuBtn.OnTapped = func() {
// if pop == nil {
// menu := fyne.NewMenu("", a.shareMenuItem)
// pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a))
// }
// pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(a.menuBtn)
// pop.ShowAtPosition(fyne.NewPos(pos.X, pos.Y+a.menuBtn.Size().Height))
// }
// canShareArtists := false
// if r, canShare := a.artistPage.mp.(mediaprovider.SupportsSharing); canShare {
// canShareArtists = r.CanShareArtists()
// }
// a.shareMenuItem.Disabled = !canShareArtists
a.biographyDisp.Wrapping = fyne.TextWrapWord
a.biographyDisp.Truncation = fyne.TextTruncateEllipsis
a.ExtendBaseWidget(a)
@@ -407,12 +433,17 @@ func (a *ArtistPageHeader) toggleFavorited() {
}
func (a *ArtistPageHeader) createContainer() {
btnContainer := container.NewHBox(util.NewHSpace(2), a.favoriteBtn, a.playBtn, a.playRadioBtn)
if a.menuBtn != nil {
btnContainer.Add(a.menuBtn)
}
a.container = util.AddHeaderBackground(
container.NewBorder(nil, nil, a.artistImage, nil,
container.NewVBox(
container.New(&layouts.VboxCustomPadding{ExtraPad: -10},
a.titleDisp, a.biographyDisp, a.similarArtists),
container.NewHBox(util.NewHSpace(2), a.favoriteBtn, a.playBtn, a.playRadioBtn)),
btnContainer),
))
}
+5
View File
@@ -86,6 +86,11 @@ func newArtistsPage(
} else {
a.grid = widgets.NewFixedGridView(nil, a.im, myTheme.ArtistIcon)
}
canShareArtists := false
if r, canShare := mp.(mediaprovider.SupportsSharing); canShare {
canShareArtists = r.CanShareArtists()
}
a.grid.DisableSharing = !canShareArtists
a.contr.ConnectArtistGridActions(a.grid)
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
+7
View File
@@ -336,6 +336,11 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
} else {
a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon)
}
canShareArtists := false
if r, canShare := a.mp.(mediaprovider.SupportsSharing); canShare {
canShareArtists = r.CanShareArtists()
}
a.artistGrid.DisableSharing = !canShareArtists
a.contr.ConnectArtistGridActions(a.artistGrid)
a.container.Objects[0] = a.artistGrid
a.Refresh()
@@ -395,7 +400,9 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
}
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
tracklist.Options.DisableRating = !canRate
tracklist.Options.DisableSharing = !canShare
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
tracklist.SetSorting(a.trackSort)
tracklist.OnVisibleColumnsChanged = func(cols []string) {
+2
View File
@@ -112,6 +112,7 @@ func NewGridViewPage(
gp.ExtendBaseWidget(gp)
gp.createTitleAndSort()
_, canShare := mp.(mediaprovider.SupportsSharing)
iter := adapter.Iter(gp.getSortOrder(), gp.getFilter())
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
gp.grid = g.(*widgets.GridView)
@@ -120,6 +121,7 @@ func NewGridViewPage(
} else {
gp.grid = widgets.NewGridView(iter, im, adapter.PlaceholderResource())
}
gp.grid.DisableSharing = !canShare
adapter.ConnectGridActions(gp.grid)
gp.createSearchAndFilter()
gp.createContainer()
+16 -7
View File
@@ -43,11 +43,12 @@ type NowPlayingPage struct {
}
type nowPlayingPageState struct {
contr *controller.Controller
pool *util.WidgetPool
pm *backend.PlaybackManager
im *backend.ImageManager
canRate bool
contr *controller.Controller
pool *util.WidgetPool
pm *backend.PlaybackManager
im *backend.ImageManager
canRate bool
canShare bool
}
func NewNowPlayingPage(
@@ -56,9 +57,10 @@ func NewNowPlayingPage(
im *backend.ImageManager,
pm *backend.PlaybackManager,
canRate bool,
canShare bool,
) *NowPlayingPage {
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{
contr: contr, pool: pool, im: im, pm: pm, canRate: canRate,
contr: contr, pool: pool, im: im, pm: pm, canRate: canRate, canShare: canShare,
}}
a.ExtendBaseWidget(a)
@@ -82,8 +84,15 @@ func NewNowPlayingPage(
}
a.queueList = widgets.NewPlayQueueList(a.im)
a.queueList.DisableRating = !canRate
a.queueList.DisableSharing = !canShare
a.queueList.OnReorderTracks = a.doSetNewTrackOrder
a.queueList.OnDownload = contr.ShowDownloadDialog
a.queueList.OnShare = func(tracks []*mediaprovider.Track) {
if len(tracks) > 0 {
a.contr.ShowShareDialog(tracks[0].ID)
}
}
a.queueList.OnAddToPlaylist = contr.DoAddTracksToPlaylistWorkflow
a.queueList.OnPlayTrackAt = func(tracknum int) {
_ = a.pm.PlayTrackAt(tracknum)
@@ -199,7 +208,7 @@ func (s *nowPlayingPageState) Restore() Page {
page.Reload()
return page
}
return NewNowPlayingPage(s.contr, s.pool, s.im, s.pm, s.canRate)
return NewNowPlayingPage(s.contr, s.pool, s.im, s.pm, s.canRate, s.canShare)
}
var _ CanShowPlayTime = (*NowPlayingPage)(nil)
+3 -1
View File
@@ -87,10 +87,12 @@ func newPlaylistPage(
conf.TracklistColumns = cols
}
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
remove.Icon = theme.ContentClearIcon()
a.tracklist.Options = widgets.TracklistOptions{
DisableRating: !canRate,
DisableRating: !canRate,
DisableSharing: !canShare,
AuxiliaryMenuItems: []*fyne.MenuItem{
util.NewReorderTracksSubmenu(a.doSetNewTrackOrder),
remove,
+3 -2
View File
@@ -31,6 +31,7 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
func (r Router) CreatePage(rte controller.Route) Page {
_, canRate := r.App.ServerManager.Server.(mediaprovider.SupportsRating)
_, canShare := r.App.ServerManager.Server.(mediaprovider.SupportsSharing)
switch rte.Page {
case controller.Album:
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
@@ -43,13 +44,13 @@ func (r Router) CreatePage(rte controller.Route) Page {
case controller.Favorites:
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
case controller.Fullscreen:
return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate)
return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate, canShare)
case controller.Genre:
return NewGenrePage(rte.Arg, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Genres:
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
case controller.NowPlaying:
return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate)
return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate, canShare)
case controller.Playlist:
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
case controller.Playlists:
+4
View File
@@ -40,6 +40,7 @@ type tracksPageState struct {
conf *backend.TracksPageConfig
mp mediaprovider.MediaProvider
canRate bool
canShare bool
}
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage {
@@ -48,9 +49,11 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
t.tracklist = t.obtainTracklist()
_, t.canRate = mp.(mediaprovider.SupportsRating)
_, t.canShare = mp.(mediaprovider.SupportsSharing)
t.tracklist.Options = widgets.TracklistOptions{
DisableSorting: true,
DisableRating: !t.canRate,
DisableSharing: !t.canShare,
AutoNumber: true,
}
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
@@ -137,6 +140,7 @@ func (t *TracksPage) doSearch(query string) {
AutoNumber: true,
DisableSorting: true,
DisableRating: !t.canRate,
DisableSharing: !t.canShare,
}
t.searchTracklist.SetVisibleColumns(t.conf.TracklistColumns)
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
+60
View File
@@ -7,6 +7,7 @@ import (
"image"
"io"
"log"
"net/url"
"os"
"path/filepath"
"time"
@@ -22,8 +23,10 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
@@ -138,6 +141,9 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget
m.ClosePopUpOnEscape(pop)
}
tracklist.OnDownload = m.ShowDownloadDialog
tracklist.OnShare = func(trackID string) {
go m.ShowShareDialog(trackID)
}
}
func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
@@ -173,6 +179,9 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
m.ShowDownloadDialog(album.Tracks, album.Name)
}()
}
grid.OnShare = func(albumID string) {
go m.ShowShareDialog(albumID)
}
}
func (m *Controller) ConnectArtistGridActions(grid *widgets.GridView) {
@@ -662,6 +671,52 @@ func (c *Controller) SetTrackRatings(trackIDs []string, rating int) {
}
}
func (c *Controller) ShowShareDialog(id string) {
go func() {
shareUrl, err := c.createShareURL(id)
if err != nil {
return
}
hyperlink := widget.NewHyperlink(shareUrl.String(), shareUrl)
dlg := dialog.NewCustom("Share content", "OK",
container.NewHBox(
hyperlink,
widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {
c.MainWindow.Clipboard().SetContent(hyperlink.Text)
}),
widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() {
if shareUrl, err := c.createShareURL(id); err == nil {
hyperlink.Text = shareUrl.String()
hyperlink.URL = shareUrl
hyperlink.Refresh()
}
}),
),
c.MainWindow,
)
dlg.Show()
}()
}
func (c *Controller) createShareURL(id string) (*url.URL, error) {
r, ok := c.App.ServerManager.Server.(mediaprovider.SupportsSharing)
if !ok {
return nil, fmt.Errorf("server does not support sharing")
}
shareUrl, err := r.CreateShareURL(id)
if err != nil {
log.Printf("error creating share URL: %v", err)
c.showError(
"Failed to share content. This commonly occurs when the server does not support sharing," +
"or has the feature disabled.\nPlease check the server's settings and try again.",
)
return nil, err
}
return shareUrl, nil
}
func (c *Controller) ShowDownloadDialog(tracks []*mediaprovider.Track, downloadName string) {
numTracks := len(tracks)
var fileName string
@@ -762,6 +817,11 @@ func (c *Controller) sendNotification(title, content string) {
})
}
func (c *Controller) showError(content string) {
// TODO: display an in-app toast message instead of a dialog.
dialog.ShowError(fmt.Errorf(content), c.MainWindow)
}
func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover image.Image) {
go func() {
albumInfo, err := c.App.ServerManager.Server.GetAlbumInfo(albumID)
+1
View File
@@ -31,6 +31,7 @@ var (
NotFavoriteIcon fyne.Resource = theme.NewThemedResource(res.ResHeartOutlineSvg)
NowPlayingIcon fyne.Resource = theme.NewThemedResource(res.ResHeadphonesSvg)
PlaylistIcon fyne.Resource = theme.NewThemedResource(res.ResPlaylistSvg)
ShareIcon fyne.Resource = theme.NewThemedResource(res.ResShareSvg)
ShuffleIcon fyne.Resource = theme.NewThemedResource(res.ResShuffleSvg)
TracksIcon fyne.Resource = theme.NewThemedResource(res.ResMusicnotesSvg)
GenreIcon fyne.Resource = theme.NewThemedResource(res.ResTheatermasksSvg)
+10 -2
View File
@@ -79,6 +79,7 @@ type GridView struct {
itemForIndex map[int]*GridViewItem
itemWidth float32
numColsCached int
shareMenuItem *fyne.MenuItem
}
type GridViewState struct {
@@ -89,10 +90,13 @@ type GridViewState struct {
highestShown int
done bool
DisableSharing bool
OnPlay func(id string, shuffle bool)
OnAddToQueue func(id string)
OnAddToPlaylist func(id string)
OnDownload func(id string)
OnShare func(id string)
OnShowItemPage func(id string)
OnShowSecondaryPage func(id string)
@@ -407,10 +411,14 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) {
}
})
download.Icon = theme.DownloadIcon()
g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queue, playlist, download),
g.shareMenuItem = fyne.NewMenuItem("Share...", func() {
g.OnShare(g.menuGridViewItemId)
})
g.shareMenuItem.Icon = myTheme.ShareIcon
g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queue, playlist, download, g.shareMenuItem),
fyne.CurrentApp().Driver().CanvasForObject(g))
}
g.shareMenuItem.Disabled = g.DisableSharing
g.menu.ShowAtPosition(pos)
}
+12 -1
View File
@@ -26,7 +26,8 @@ const thumbnailSize = 52
type PlayQueueList struct {
widget.BaseWidget
DisableRating bool
DisableRating bool
DisableSharing bool
// user action callbacks
OnAddToPlaylist func(trackIDs []string)
@@ -34,6 +35,7 @@ type PlayQueueList struct {
OnSetRating func(trackIDs []string, rating int)
OnRemoveFromQueue func(trackIDs []string)
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
OnShare func(tracks []*mediaprovider.Track)
OnShowArtistPage func(artistID string)
OnPlayTrackAt func(idx int)
OnReorderTracks func(trackIDs []string, op sharedutil.TrackReorderOp)
@@ -41,6 +43,7 @@ type PlayQueueList struct {
list *FocusList
menu *widget.PopUpMenu
ratingSubmenu *fyne.MenuItem
shareMenuItem *fyne.MenuItem
nowPlayingID string
colLayout *layouts.ColumnsLayout
@@ -200,6 +203,12 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
}
})
download.Icon = theme.DownloadIcon()
p.shareMenuItem = fyne.NewMenuItem("Share...", func() {
if p.OnShare != nil {
p.OnShare(p.selectedTracks())
}
})
p.shareMenuItem.Icon = myTheme.ShareIcon
favorite := fyne.NewMenuItem("Set favorite", func() {
if p.OnSetFavorite != nil {
p.OnSetFavorite(p.selectedTrackIDs(), true)
@@ -233,6 +242,7 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
fyne.NewMenu("",
playlist,
download,
p.shareMenuItem,
fyne.NewMenuItemSeparator(),
favorite,
unfavorite,
@@ -245,6 +255,7 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
)
}
p.ratingSubmenu.Disabled = p.DisableRating
p.shareMenuItem.Disabled = p.DisableSharing || len(p.selectedTracks()) != 1
p.menu.ShowAtPosition(e.AbsolutePosition)
}
+19
View File
@@ -72,6 +72,9 @@ type TracklistOptions struct {
// Disables the five star rating widget.
DisableRating bool
// Disables the sharing option.
DisableSharing bool
}
type Tracklist struct {
@@ -87,6 +90,7 @@ type Tracklist struct {
OnSetFavorite func(trackIDs []string, fav bool)
OnSetRating func(trackIDs []string, rating int)
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
OnShare func(trackID string)
OnShowArtistPage func(artistID string)
OnShowAlbumPage func(albumID string)
@@ -108,6 +112,7 @@ type Tracklist struct {
list *FocusList
ctxMenu *fyne.Menu
ratingSubmenu *fyne.MenuItem
shareMenuItem *fyne.MenuItem
container *fyne.Container
}
@@ -555,6 +560,11 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
})
unfavorite.Icon = myTheme.NotFavoriteIcon
t.ctxMenu.Items = append(t.ctxMenu.Items, playlist, download)
t.shareMenuItem = fyne.NewMenuItem("Share...", func() {
t.onShare(t.selectedTracks())
})
t.shareMenuItem.Icon = myTheme.ShareIcon
t.ctxMenu.Items = append(t.ctxMenu.Items, t.shareMenuItem)
t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItemSeparator())
t.ctxMenu.Items = append(t.ctxMenu.Items, favorite, unfavorite)
t.ratingSubmenu = util.NewRatingSubmenu(func(rating int) {
@@ -567,6 +577,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
}
}
t.ratingSubmenu.Disabled = t.Options.DisableRating
t.shareMenuItem.Disabled = t.Options.DisableSharing || len(t.selectedTracks()) != 1
widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition)
}
@@ -629,6 +640,14 @@ func (t *Tracklist) onDownload(tracks []*mediaprovider.Track, downloadName strin
}
}
func (t *Tracklist) onShare(tracks []*mediaprovider.Track) {
if t.OnShare != nil {
if len(tracks) > 0 {
t.OnShare(tracks[0].ID)
}
}
}
func (t *Tracklist) selectedTracks() []*mediaprovider.Track {
t.tracksMutex.RLock()
defer t.tracksMutex.RUnlock()