Merge pull request #336 from adamantike/feat/add-share-option
feat: Add Share option for artists, albums, and tracks
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user