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 (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,6 +149,11 @@ type SupportsRating interface {
|
|||||||
SetRating(params RatingFavoriteParameters, rating int) error
|
SetRating(params RatingFavoriteParameters, rating int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SupportsSharing interface {
|
||||||
|
CreateShareURL(id string) (*url.URL, error)
|
||||||
|
CanShareArtists() bool
|
||||||
|
}
|
||||||
|
|
||||||
type JukeboxProvider interface {
|
type JukeboxProvider interface {
|
||||||
JukeboxStart() error
|
JukeboxStart() error
|
||||||
JukeboxStop() error
|
JukeboxStop() error
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -315,6 +316,26 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar
|
|||||||
return err
|
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) {
|
func (s *subsonicMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
||||||
return s.client.Download(trackID)
|
return s.client.Download(trackID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc
|
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc
|
||||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
|
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/fsnotify/fsnotify v1.6.0
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/google/uuid v1.3.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-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 h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY=
|
||||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0=
|
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-20240225165422-3617754d751b h1:LGUWXRcndcWwBN0HTFsCUlIjr8aakRsi9FlUX1CPSs8=
|
||||||
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/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.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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ var ResShuffleSvg = &fyne.StaticResource{
|
|||||||
StaticContent: []byte(
|
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>"),
|
"<?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{
|
var ResUpdownarrowSvg = &fyne.StaticResource{
|
||||||
StaticName: "updownarrow.svg",
|
StaticName: "updownarrow.svg",
|
||||||
StaticContent: []byte(
|
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/repeat.svg >> bundled.go
|
||||||
fyne bundle -append -prefix Res icons/remix_design/repeatone.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/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 icons/remix_design/updownarrow.svg >> bundled.go
|
||||||
|
|
||||||
fyne bundle -append -prefix Res themes/default.toml >> 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.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
a.tracklist.SetSorting(sort)
|
a.tracklist.SetSorting(sort)
|
||||||
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
||||||
|
_, canShare := a.mp.(mediaprovider.SupportsSharing)
|
||||||
a.tracklist.Options.DisableRating = !canRate
|
a.tracklist.Options.DisableRating = !canRate
|
||||||
|
a.tracklist.Options.DisableSharing = !canShare
|
||||||
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
a.cfg.TracklistColumns = cols
|
a.cfg.TracklistColumns = cols
|
||||||
}
|
}
|
||||||
@@ -183,6 +185,7 @@ type AlbumPageHeader struct {
|
|||||||
artistLabelSpace *util.HSpace // TODO: remove when no longer needed
|
artistLabelSpace *util.HSpace // TODO: remove when no longer needed
|
||||||
genreLabel *widgets.MultiHyperlink
|
genreLabel *widgets.MultiHyperlink
|
||||||
miscLabel *widget.Label
|
miscLabel *widget.Label
|
||||||
|
shareMenuItem *fyne.MenuItem
|
||||||
|
|
||||||
toggleFavButton *widgets.FavoriteButton
|
toggleFavButton *widgets.FavoriteButton
|
||||||
|
|
||||||
@@ -246,9 +249,15 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
|
|||||||
a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image())
|
a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image())
|
||||||
})
|
})
|
||||||
info.Icon = theme.InfoIcon()
|
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))
|
pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a))
|
||||||
}
|
}
|
||||||
|
_, canShare := page.mp.(mediaprovider.SupportsSharing)
|
||||||
|
a.shareMenuItem.Disabled = !canShare
|
||||||
pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn)
|
pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn)
|
||||||
pop.ShowAtPosition(fyne.NewPos(pos.X, pos.Y+menuBtn.Size().Height))
|
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}
|
tl.Options = widgets.TracklistOptions{AutoNumber: true}
|
||||||
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
||||||
|
_, canShare := a.mp.(mediaprovider.SupportsSharing)
|
||||||
tl.Options.DisableRating = !canRate
|
tl.Options.DisableRating = !canRate
|
||||||
|
tl.Options.DisableSharing = !canShare
|
||||||
tl.SetVisibleColumns(a.cfg.TracklistColumns)
|
tl.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
tl.SetSorting(a.trackSort)
|
tl.SetSorting(a.trackSort)
|
||||||
tl.OnVisibleColumnsChanged = func(cols []string) {
|
tl.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -292,7 +294,9 @@ type ArtistPageHeader struct {
|
|||||||
favoriteBtn *widgets.FavoriteButton
|
favoriteBtn *widgets.FavoriteButton
|
||||||
playBtn *widget.Button
|
playBtn *widget.Button
|
||||||
playRadioBtn *widget.Button
|
playRadioBtn *widget.Button
|
||||||
|
menuBtn *widget.Button
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
|
shareMenuItem *fyne.MenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
|
func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
|
||||||
@@ -318,6 +322,28 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
|
|||||||
go a.artistPage.contr.PlayArtistDiscography(a.artistID, false /*shuffle*/)
|
go a.artistPage.contr.PlayArtistDiscography(a.artistID, false /*shuffle*/)
|
||||||
})
|
})
|
||||||
a.playRadioBtn = widget.NewButtonWithIcon("Play Artist Radio", myTheme.ShuffleIcon, a.artistPage.playArtistRadio)
|
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.Wrapping = fyne.TextWrapWord
|
||||||
a.biographyDisp.Truncation = fyne.TextTruncateEllipsis
|
a.biographyDisp.Truncation = fyne.TextTruncateEllipsis
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
@@ -407,12 +433,17 @@ func (a *ArtistPageHeader) toggleFavorited() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArtistPageHeader) createContainer() {
|
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(
|
a.container = util.AddHeaderBackground(
|
||||||
container.NewBorder(nil, nil, a.artistImage, nil,
|
container.NewBorder(nil, nil, a.artistImage, nil,
|
||||||
container.NewVBox(
|
container.NewVBox(
|
||||||
container.New(&layouts.VboxCustomPadding{ExtraPad: -10},
|
container.New(&layouts.VboxCustomPadding{ExtraPad: -10},
|
||||||
a.titleDisp, a.biographyDisp, a.similarArtists),
|
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 {
|
} else {
|
||||||
a.grid = widgets.NewFixedGridView(nil, a.im, myTheme.ArtistIcon)
|
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)
|
a.contr.ConnectArtistGridActions(a.grid)
|
||||||
|
|
||||||
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
||||||
|
|||||||
@@ -336,6 +336,11 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
|
|||||||
} else {
|
} else {
|
||||||
a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon)
|
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.contr.ConnectArtistGridActions(a.artistGrid)
|
||||||
a.container.Objects[0] = a.artistGrid
|
a.container.Objects[0] = a.artistGrid
|
||||||
a.Refresh()
|
a.Refresh()
|
||||||
@@ -395,7 +400,9 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
|
|||||||
}
|
}
|
||||||
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
|
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
|
||||||
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
_, canRate := a.mp.(mediaprovider.SupportsRating)
|
||||||
|
_, canShare := a.mp.(mediaprovider.SupportsSharing)
|
||||||
tracklist.Options.DisableRating = !canRate
|
tracklist.Options.DisableRating = !canRate
|
||||||
|
tracklist.Options.DisableSharing = !canShare
|
||||||
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
tracklist.SetSorting(a.trackSort)
|
tracklist.SetSorting(a.trackSort)
|
||||||
tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ func NewGridViewPage(
|
|||||||
gp.ExtendBaseWidget(gp)
|
gp.ExtendBaseWidget(gp)
|
||||||
gp.createTitleAndSort()
|
gp.createTitleAndSort()
|
||||||
|
|
||||||
|
_, canShare := mp.(mediaprovider.SupportsSharing)
|
||||||
iter := adapter.Iter(gp.getSortOrder(), gp.getFilter())
|
iter := adapter.Iter(gp.getSortOrder(), gp.getFilter())
|
||||||
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
|
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
gp.grid = g.(*widgets.GridView)
|
gp.grid = g.(*widgets.GridView)
|
||||||
@@ -120,6 +121,7 @@ func NewGridViewPage(
|
|||||||
} else {
|
} else {
|
||||||
gp.grid = widgets.NewGridView(iter, im, adapter.PlaceholderResource())
|
gp.grid = widgets.NewGridView(iter, im, adapter.PlaceholderResource())
|
||||||
}
|
}
|
||||||
|
gp.grid.DisableSharing = !canShare
|
||||||
adapter.ConnectGridActions(gp.grid)
|
adapter.ConnectGridActions(gp.grid)
|
||||||
gp.createSearchAndFilter()
|
gp.createSearchAndFilter()
|
||||||
gp.createContainer()
|
gp.createContainer()
|
||||||
|
|||||||
@@ -43,11 +43,12 @@ type NowPlayingPage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type nowPlayingPageState struct {
|
type nowPlayingPageState struct {
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
pool *util.WidgetPool
|
pool *util.WidgetPool
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
canRate bool
|
canRate bool
|
||||||
|
canShare bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNowPlayingPage(
|
func NewNowPlayingPage(
|
||||||
@@ -56,9 +57,10 @@ func NewNowPlayingPage(
|
|||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
canRate bool,
|
canRate bool,
|
||||||
|
canShare bool,
|
||||||
) *NowPlayingPage {
|
) *NowPlayingPage {
|
||||||
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{
|
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)
|
a.ExtendBaseWidget(a)
|
||||||
|
|
||||||
@@ -82,8 +84,15 @@ func NewNowPlayingPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.queueList = widgets.NewPlayQueueList(a.im)
|
a.queueList = widgets.NewPlayQueueList(a.im)
|
||||||
|
a.queueList.DisableRating = !canRate
|
||||||
|
a.queueList.DisableSharing = !canShare
|
||||||
a.queueList.OnReorderTracks = a.doSetNewTrackOrder
|
a.queueList.OnReorderTracks = a.doSetNewTrackOrder
|
||||||
a.queueList.OnDownload = contr.ShowDownloadDialog
|
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.OnAddToPlaylist = contr.DoAddTracksToPlaylistWorkflow
|
||||||
a.queueList.OnPlayTrackAt = func(tracknum int) {
|
a.queueList.OnPlayTrackAt = func(tracknum int) {
|
||||||
_ = a.pm.PlayTrackAt(tracknum)
|
_ = a.pm.PlayTrackAt(tracknum)
|
||||||
@@ -199,7 +208,7 @@ func (s *nowPlayingPageState) Restore() Page {
|
|||||||
page.Reload()
|
page.Reload()
|
||||||
return page
|
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)
|
var _ CanShowPlayTime = (*NowPlayingPage)(nil)
|
||||||
|
|||||||
@@ -87,10 +87,12 @@ func newPlaylistPage(
|
|||||||
conf.TracklistColumns = cols
|
conf.TracklistColumns = cols
|
||||||
}
|
}
|
||||||
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
|
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
|
||||||
|
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
|
||||||
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
|
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
|
||||||
remove.Icon = theme.ContentClearIcon()
|
remove.Icon = theme.ContentClearIcon()
|
||||||
a.tracklist.Options = widgets.TracklistOptions{
|
a.tracklist.Options = widgets.TracklistOptions{
|
||||||
DisableRating: !canRate,
|
DisableRating: !canRate,
|
||||||
|
DisableSharing: !canShare,
|
||||||
AuxiliaryMenuItems: []*fyne.MenuItem{
|
AuxiliaryMenuItems: []*fyne.MenuItem{
|
||||||
util.NewReorderTracksSubmenu(a.doSetNewTrackOrder),
|
util.NewReorderTracksSubmenu(a.doSetNewTrackOrder),
|
||||||
remove,
|
remove,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
|
|||||||
|
|
||||||
func (r Router) CreatePage(rte controller.Route) Page {
|
func (r Router) CreatePage(rte controller.Route) Page {
|
||||||
_, canRate := r.App.ServerManager.Server.(mediaprovider.SupportsRating)
|
_, canRate := r.App.ServerManager.Server.(mediaprovider.SupportsRating)
|
||||||
|
_, canShare := r.App.ServerManager.Server.(mediaprovider.SupportsSharing)
|
||||||
switch rte.Page {
|
switch rte.Page {
|
||||||
case controller.Album:
|
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)
|
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:
|
case controller.Favorites:
|
||||||
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
|
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
|
||||||
case controller.Fullscreen:
|
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:
|
case controller.Genre:
|
||||||
return NewGenrePage(rte.Arg, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
return NewGenrePage(rte.Arg, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
||||||
case controller.Genres:
|
case controller.Genres:
|
||||||
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
|
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
|
||||||
case controller.NowPlaying:
|
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:
|
case controller.Playlist:
|
||||||
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
|
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
|
||||||
case controller.Playlists:
|
case controller.Playlists:
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type tracksPageState struct {
|
|||||||
conf *backend.TracksPageConfig
|
conf *backend.TracksPageConfig
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
canRate bool
|
canRate bool
|
||||||
|
canShare bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage {
|
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.tracklist = t.obtainTracklist()
|
||||||
_, t.canRate = mp.(mediaprovider.SupportsRating)
|
_, t.canRate = mp.(mediaprovider.SupportsRating)
|
||||||
|
_, t.canShare = mp.(mediaprovider.SupportsSharing)
|
||||||
t.tracklist.Options = widgets.TracklistOptions{
|
t.tracklist.Options = widgets.TracklistOptions{
|
||||||
DisableSorting: true,
|
DisableSorting: true,
|
||||||
DisableRating: !t.canRate,
|
DisableRating: !t.canRate,
|
||||||
|
DisableSharing: !t.canShare,
|
||||||
AutoNumber: true,
|
AutoNumber: true,
|
||||||
}
|
}
|
||||||
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
||||||
@@ -137,6 +140,7 @@ func (t *TracksPage) doSearch(query string) {
|
|||||||
AutoNumber: true,
|
AutoNumber: true,
|
||||||
DisableSorting: true,
|
DisableSorting: true,
|
||||||
DisableRating: !t.canRate,
|
DisableRating: !t.canRate,
|
||||||
|
DisableSharing: !t.canShare,
|
||||||
}
|
}
|
||||||
t.searchTracklist.SetVisibleColumns(t.conf.TracklistColumns)
|
t.searchTracklist.SetVisibleColumns(t.conf.TracklistColumns)
|
||||||
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
|
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -22,8 +23,10 @@ import (
|
|||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/canvas"
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/dialog"
|
"fyne.io/fyne/v2/dialog"
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,6 +141,9 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget
|
|||||||
m.ClosePopUpOnEscape(pop)
|
m.ClosePopUpOnEscape(pop)
|
||||||
}
|
}
|
||||||
tracklist.OnDownload = m.ShowDownloadDialog
|
tracklist.OnDownload = m.ShowDownloadDialog
|
||||||
|
tracklist.OnShare = func(trackID string) {
|
||||||
|
go m.ShowShareDialog(trackID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
|
func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
|
||||||
@@ -173,6 +179,9 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
|
|||||||
m.ShowDownloadDialog(album.Tracks, album.Name)
|
m.ShowDownloadDialog(album.Tracks, album.Name)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
grid.OnShare = func(albumID string) {
|
||||||
|
go m.ShowShareDialog(albumID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Controller) ConnectArtistGridActions(grid *widgets.GridView) {
|
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) {
|
func (c *Controller) ShowDownloadDialog(tracks []*mediaprovider.Track, downloadName string) {
|
||||||
numTracks := len(tracks)
|
numTracks := len(tracks)
|
||||||
var fileName string
|
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) {
|
func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover image.Image) {
|
||||||
go func() {
|
go func() {
|
||||||
albumInfo, err := c.App.ServerManager.Server.GetAlbumInfo(albumID)
|
albumInfo, err := c.App.ServerManager.Server.GetAlbumInfo(albumID)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ var (
|
|||||||
NotFavoriteIcon fyne.Resource = theme.NewThemedResource(res.ResHeartOutlineSvg)
|
NotFavoriteIcon fyne.Resource = theme.NewThemedResource(res.ResHeartOutlineSvg)
|
||||||
NowPlayingIcon fyne.Resource = theme.NewThemedResource(res.ResHeadphonesSvg)
|
NowPlayingIcon fyne.Resource = theme.NewThemedResource(res.ResHeadphonesSvg)
|
||||||
PlaylistIcon fyne.Resource = theme.NewThemedResource(res.ResPlaylistSvg)
|
PlaylistIcon fyne.Resource = theme.NewThemedResource(res.ResPlaylistSvg)
|
||||||
|
ShareIcon fyne.Resource = theme.NewThemedResource(res.ResShareSvg)
|
||||||
ShuffleIcon fyne.Resource = theme.NewThemedResource(res.ResShuffleSvg)
|
ShuffleIcon fyne.Resource = theme.NewThemedResource(res.ResShuffleSvg)
|
||||||
TracksIcon fyne.Resource = theme.NewThemedResource(res.ResMusicnotesSvg)
|
TracksIcon fyne.Resource = theme.NewThemedResource(res.ResMusicnotesSvg)
|
||||||
GenreIcon fyne.Resource = theme.NewThemedResource(res.ResTheatermasksSvg)
|
GenreIcon fyne.Resource = theme.NewThemedResource(res.ResTheatermasksSvg)
|
||||||
|
|||||||
+10
-2
@@ -79,6 +79,7 @@ type GridView struct {
|
|||||||
itemForIndex map[int]*GridViewItem
|
itemForIndex map[int]*GridViewItem
|
||||||
itemWidth float32
|
itemWidth float32
|
||||||
numColsCached int
|
numColsCached int
|
||||||
|
shareMenuItem *fyne.MenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
type GridViewState struct {
|
type GridViewState struct {
|
||||||
@@ -89,10 +90,13 @@ type GridViewState struct {
|
|||||||
highestShown int
|
highestShown int
|
||||||
done bool
|
done bool
|
||||||
|
|
||||||
|
DisableSharing bool
|
||||||
|
|
||||||
OnPlay func(id string, shuffle bool)
|
OnPlay func(id string, shuffle bool)
|
||||||
OnAddToQueue func(id string)
|
OnAddToQueue func(id string)
|
||||||
OnAddToPlaylist func(id string)
|
OnAddToPlaylist func(id string)
|
||||||
OnDownload func(id string)
|
OnDownload func(id string)
|
||||||
|
OnShare func(id string)
|
||||||
OnShowItemPage func(id string)
|
OnShowItemPage func(id string)
|
||||||
OnShowSecondaryPage func(id string)
|
OnShowSecondaryPage func(id string)
|
||||||
|
|
||||||
@@ -407,10 +411,14 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
download.Icon = theme.DownloadIcon()
|
download.Icon = theme.DownloadIcon()
|
||||||
|
g.shareMenuItem = fyne.NewMenuItem("Share...", func() {
|
||||||
g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queue, playlist, download),
|
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))
|
fyne.CurrentApp().Driver().CanvasForObject(g))
|
||||||
}
|
}
|
||||||
|
g.shareMenuItem.Disabled = g.DisableSharing
|
||||||
g.menu.ShowAtPosition(pos)
|
g.menu.ShowAtPosition(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ const thumbnailSize = 52
|
|||||||
type PlayQueueList struct {
|
type PlayQueueList struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
DisableRating bool
|
DisableRating bool
|
||||||
|
DisableSharing bool
|
||||||
|
|
||||||
// user action callbacks
|
// user action callbacks
|
||||||
OnAddToPlaylist func(trackIDs []string)
|
OnAddToPlaylist func(trackIDs []string)
|
||||||
@@ -34,6 +35,7 @@ type PlayQueueList struct {
|
|||||||
OnSetRating func(trackIDs []string, rating int)
|
OnSetRating func(trackIDs []string, rating int)
|
||||||
OnRemoveFromQueue func(trackIDs []string)
|
OnRemoveFromQueue func(trackIDs []string)
|
||||||
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
||||||
|
OnShare func(tracks []*mediaprovider.Track)
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnPlayTrackAt func(idx int)
|
OnPlayTrackAt func(idx int)
|
||||||
OnReorderTracks func(trackIDs []string, op sharedutil.TrackReorderOp)
|
OnReorderTracks func(trackIDs []string, op sharedutil.TrackReorderOp)
|
||||||
@@ -41,6 +43,7 @@ type PlayQueueList struct {
|
|||||||
list *FocusList
|
list *FocusList
|
||||||
menu *widget.PopUpMenu
|
menu *widget.PopUpMenu
|
||||||
ratingSubmenu *fyne.MenuItem
|
ratingSubmenu *fyne.MenuItem
|
||||||
|
shareMenuItem *fyne.MenuItem
|
||||||
|
|
||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
colLayout *layouts.ColumnsLayout
|
colLayout *layouts.ColumnsLayout
|
||||||
@@ -200,6 +203,12 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
download.Icon = theme.DownloadIcon()
|
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() {
|
favorite := fyne.NewMenuItem("Set favorite", func() {
|
||||||
if p.OnSetFavorite != nil {
|
if p.OnSetFavorite != nil {
|
||||||
p.OnSetFavorite(p.selectedTrackIDs(), true)
|
p.OnSetFavorite(p.selectedTrackIDs(), true)
|
||||||
@@ -233,6 +242,7 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
fyne.NewMenu("",
|
fyne.NewMenu("",
|
||||||
playlist,
|
playlist,
|
||||||
download,
|
download,
|
||||||
|
p.shareMenuItem,
|
||||||
fyne.NewMenuItemSeparator(),
|
fyne.NewMenuItemSeparator(),
|
||||||
favorite,
|
favorite,
|
||||||
unfavorite,
|
unfavorite,
|
||||||
@@ -245,6 +255,7 @@ func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
p.ratingSubmenu.Disabled = p.DisableRating
|
p.ratingSubmenu.Disabled = p.DisableRating
|
||||||
|
p.shareMenuItem.Disabled = p.DisableSharing || len(p.selectedTracks()) != 1
|
||||||
p.menu.ShowAtPosition(e.AbsolutePosition)
|
p.menu.ShowAtPosition(e.AbsolutePosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ type TracklistOptions struct {
|
|||||||
|
|
||||||
// Disables the five star rating widget.
|
// Disables the five star rating widget.
|
||||||
DisableRating bool
|
DisableRating bool
|
||||||
|
|
||||||
|
// Disables the sharing option.
|
||||||
|
DisableSharing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracklist struct {
|
type Tracklist struct {
|
||||||
@@ -87,6 +90,7 @@ type Tracklist struct {
|
|||||||
OnSetFavorite func(trackIDs []string, fav bool)
|
OnSetFavorite func(trackIDs []string, fav bool)
|
||||||
OnSetRating func(trackIDs []string, rating int)
|
OnSetRating func(trackIDs []string, rating int)
|
||||||
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
||||||
|
OnShare func(trackID string)
|
||||||
|
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnShowAlbumPage func(albumID string)
|
OnShowAlbumPage func(albumID string)
|
||||||
@@ -108,6 +112,7 @@ type Tracklist struct {
|
|||||||
list *FocusList
|
list *FocusList
|
||||||
ctxMenu *fyne.Menu
|
ctxMenu *fyne.Menu
|
||||||
ratingSubmenu *fyne.MenuItem
|
ratingSubmenu *fyne.MenuItem
|
||||||
|
shareMenuItem *fyne.MenuItem
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,6 +560,11 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
})
|
})
|
||||||
unfavorite.Icon = myTheme.NotFavoriteIcon
|
unfavorite.Icon = myTheme.NotFavoriteIcon
|
||||||
t.ctxMenu.Items = append(t.ctxMenu.Items, playlist, download)
|
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, fyne.NewMenuItemSeparator())
|
||||||
t.ctxMenu.Items = append(t.ctxMenu.Items, favorite, unfavorite)
|
t.ctxMenu.Items = append(t.ctxMenu.Items, favorite, unfavorite)
|
||||||
t.ratingSubmenu = util.NewRatingSubmenu(func(rating int) {
|
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.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)
|
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 {
|
func (t *Tracklist) selectedTracks() []*mediaprovider.Track {
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
defer t.tracksMutex.RUnlock()
|
defer t.tracksMutex.RUnlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user