add rating to tracklist, hook up set rating action
This commit is contained in:
@@ -242,6 +242,14 @@ func (p *PlaybackManager) OnTrackFavoriteStatusChanged(id string, fav bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any time the user changes the rating of a track elsewhere in the app,
|
||||||
|
// this should be called to ensure the in-memory track model is updated.
|
||||||
|
func (p *PlaybackManager) OnTrackRatingChanged(id string, rating int) {
|
||||||
|
if tr := sharedutil.FindTrackByID(id, p.playQueue); tr != nil {
|
||||||
|
tr.UserRating = rating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// trackIdxs must be sorted
|
// trackIdxs must be sorted
|
||||||
func (p *PlaybackManager) RemoveTracksFromQueue(trackIdxs []int) {
|
func (p *PlaybackManager) RemoveTracksFromQueue(trackIdxs []int) {
|
||||||
newQueue := make([]*subsonic.Child, 0, len(p.playQueue)-len(trackIdxs))
|
newQueue := make([]*subsonic.Child, 0, len(p.playQueue)-len(trackIdxs))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"supersonic/ui/dialogs"
|
"supersonic/ui/dialogs"
|
||||||
"supersonic/ui/util"
|
"supersonic/ui/util"
|
||||||
"supersonic/ui/widgets"
|
"supersonic/ui/widgets"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -111,6 +112,27 @@ func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) {
|
|||||||
m.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, fav)
|
m.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, fav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tracklist.OnSetRating = func(trackIDs []string, rating int) {
|
||||||
|
go func() {
|
||||||
|
// Subsonic doesn't allow bulk setting ratings.
|
||||||
|
// To not overwhelm the server with requests, set rating for
|
||||||
|
// only 5 tracks at a time concurrently
|
||||||
|
for i := 0; i < len(trackIDs); i += 5 {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for j := i; j < i+5 && j < len(trackIDs); j++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int) {
|
||||||
|
m.App.ServerManager.Server.SetRating(trackIDs[idx], rating)
|
||||||
|
wg.Done()
|
||||||
|
}(j)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, id := range trackIDs {
|
||||||
|
m.App.PlaybackManager.OnTrackRatingChanged(id, rating)
|
||||||
|
}
|
||||||
|
}
|
||||||
tracklist.OnShowAlbumPage = func(albumID string) {
|
tracklist.OnShowAlbumPage = func(albumID string) {
|
||||||
m.NavigateTo(AlbumRoute(albumID))
|
m.NavigateTo(AlbumRoute(albumID))
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-11
@@ -26,6 +26,7 @@ const (
|
|||||||
ColumnTime = "Time"
|
ColumnTime = "Time"
|
||||||
ColumnYear = "Year"
|
ColumnYear = "Year"
|
||||||
ColumnFavorite = "Favorite"
|
ColumnFavorite = "Favorite"
|
||||||
|
ColumnRating = "Rating"
|
||||||
ColumnPlays = "Plays"
|
ColumnPlays = "Plays"
|
||||||
ColumnBitrate = "Bitrate"
|
ColumnBitrate = "Bitrate"
|
||||||
ColumnSize = "Size"
|
ColumnSize = "Size"
|
||||||
@@ -62,6 +63,7 @@ type Tracklist struct {
|
|||||||
OnAddToQueue func(trackIDs []*subsonic.Child)
|
OnAddToQueue func(trackIDs []*subsonic.Child)
|
||||||
OnAddToPlaylist func(trackIDs []string)
|
OnAddToPlaylist func(trackIDs []string)
|
||||||
OnSetFavorite func(trackIDs []string, fav bool)
|
OnSetFavorite func(trackIDs []string, fav bool)
|
||||||
|
OnSetRating func(trackIDs []string, rating int)
|
||||||
|
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnShowAlbumPage func(albumID string)
|
OnShowAlbumPage func(albumID string)
|
||||||
@@ -83,12 +85,12 @@ type Tracklist struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTracklist(tracks []*subsonic.Child) *Tracklist {
|
func NewTracklist(tracks []*subsonic.Child) *Tracklist {
|
||||||
t := &Tracklist{Tracks: tracks, visibleColumns: make([]bool, 11)}
|
t := &Tracklist{Tracks: tracks, visibleColumns: make([]bool, 12)}
|
||||||
|
|
||||||
t.ExtendBaseWidget(t)
|
t.ExtendBaseWidget(t)
|
||||||
t.selectionMgr = util.NewListSelectionManager(t.lenTracks)
|
t.selectionMgr = util.NewListSelectionManager(t.lenTracks)
|
||||||
// #, Title, Artist, Album, Time, Year, Favorite, Plays, Bitrate, Size, Path
|
// #, Title, Artist, Album, Time, Year, Favorite, Rating, Plays, Bitrate, Size, Path
|
||||||
t.colLayout = layouts.NewColumnsLayout([]float32{40, -1, -1, -1, 60, 60, 47, 65, 75, 70, -1})
|
t.colLayout = layouts.NewColumnsLayout([]float32{40, -1, -1, -1, 60, 60, 47, 95, 65, 75, 70, -1})
|
||||||
t.buildHeader()
|
t.buildHeader()
|
||||||
t.hdr.OnColumnVisibilityChanged = t.setColumnVisible
|
t.hdr.OnColumnVisibilityChanged = t.setColumnVisible
|
||||||
t.hdr.OnColumnVisibilityMenuShown = func(pop *widget.PopUp) {
|
t.hdr.OnColumnVisibilityMenuShown = func(pop *widget.PopUp) {
|
||||||
@@ -132,6 +134,7 @@ func (t *Tracklist) buildHeader() {
|
|||||||
{Text: "Time", AlignTrailing: true, CanToggleVisible: true},
|
{Text: "Time", AlignTrailing: true, CanToggleVisible: true},
|
||||||
{Text: "Year", AlignTrailing: true, CanToggleVisible: true},
|
{Text: "Year", AlignTrailing: true, CanToggleVisible: true},
|
||||||
{Text: "Fav.", AlignTrailing: false, CanToggleVisible: true},
|
{Text: "Fav.", AlignTrailing: false, CanToggleVisible: true},
|
||||||
|
{Text: "Rating", AlignTrailing: false, CanToggleVisible: true},
|
||||||
{Text: "Plays", AlignTrailing: true, CanToggleVisible: true},
|
{Text: "Plays", AlignTrailing: true, CanToggleVisible: true},
|
||||||
{Text: "Bitrate", AlignTrailing: true, CanToggleVisible: true},
|
{Text: "Bitrate", AlignTrailing: true, CanToggleVisible: true},
|
||||||
{Text: "Size", AlignTrailing: true, CanToggleVisible: true},
|
{Text: "Size", AlignTrailing: true, CanToggleVisible: true},
|
||||||
@@ -303,6 +306,18 @@ func (t *Tracklist) onSetFavorite(trackID string, fav bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tracklist) onSetRating(trackID string, rating int) {
|
||||||
|
// update our own track model
|
||||||
|
t.tracksMutex.RLock()
|
||||||
|
tr := sharedutil.FindTrackByID(trackID, t.Tracks)
|
||||||
|
t.tracksMutex.RUnlock()
|
||||||
|
tr.UserRating = rating
|
||||||
|
// notify listener
|
||||||
|
if t.OnSetRating != nil {
|
||||||
|
t.OnSetRating([]string{trackID}, rating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tracklist) onArtistTapped(artistID string) {
|
func (t *Tracklist) onArtistTapped(artistID string) {
|
||||||
if t.OnShowArtistPage != nil {
|
if t.OnShowArtistPage != nil {
|
||||||
t.OnShowArtistPage(artistID)
|
t.OnShowArtistPage(artistID)
|
||||||
@@ -360,14 +375,16 @@ func ColNumber(colName string) int {
|
|||||||
return 5
|
return 5
|
||||||
case ColumnFavorite:
|
case ColumnFavorite:
|
||||||
return 6
|
return 6
|
||||||
case ColumnPlays:
|
case ColumnRating:
|
||||||
return 7
|
return 7
|
||||||
case ColumnBitrate:
|
case ColumnPlays:
|
||||||
return 8
|
return 8
|
||||||
case ColumnSize:
|
case ColumnBitrate:
|
||||||
return 9
|
return 9
|
||||||
case ColumnPath:
|
case ColumnSize:
|
||||||
return 10
|
return 10
|
||||||
|
case ColumnPath:
|
||||||
|
return 11
|
||||||
default:
|
default:
|
||||||
log.Printf("error: Tracklist: invalid column name %s", colName)
|
log.Printf("error: Tracklist: invalid column name %s", colName)
|
||||||
return -100
|
return -100
|
||||||
@@ -388,12 +405,14 @@ func colName(i int) string {
|
|||||||
case 6:
|
case 6:
|
||||||
return ColumnFavorite
|
return ColumnFavorite
|
||||||
case 7:
|
case 7:
|
||||||
return ColumnPlays
|
return ColumnRating
|
||||||
case 8:
|
case 8:
|
||||||
return ColumnBitrate
|
return ColumnPlays
|
||||||
case 9:
|
case 9:
|
||||||
return ColumnSize
|
return ColumnBitrate
|
||||||
case 10:
|
case 10:
|
||||||
|
return ColumnSize
|
||||||
|
case 11:
|
||||||
return ColumnPath
|
return ColumnPath
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -421,6 +440,7 @@ type TrackRow struct {
|
|||||||
dur *widget.RichText
|
dur *widget.RichText
|
||||||
year *widget.RichText
|
year *widget.RichText
|
||||||
favorite *fyne.Container
|
favorite *fyne.Container
|
||||||
|
rating *StarRating
|
||||||
bitrate *widget.RichText
|
bitrate *widget.RichText
|
||||||
plays *widget.RichText
|
plays *widget.RichText
|
||||||
size *widget.RichText
|
size *widget.RichText
|
||||||
@@ -449,6 +469,9 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow
|
|||||||
favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng)
|
favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng)
|
||||||
favorite.OnTapped = t.toggleFavorited
|
favorite.OnTapped = t.toggleFavorited
|
||||||
t.favorite = container.NewCenter(favorite)
|
t.favorite = container.NewCenter(favorite)
|
||||||
|
t.rating = NewStarRating()
|
||||||
|
t.rating.StarSize = 16
|
||||||
|
t.rating.OnRatingChanged = t.setTrackRating
|
||||||
t.plays = widget.NewRichTextWithText("")
|
t.plays = widget.NewRichTextWithText("")
|
||||||
t.plays.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing
|
t.plays.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing
|
||||||
t.bitrate = widget.NewRichTextWithText("")
|
t.bitrate = widget.NewRichTextWithText("")
|
||||||
@@ -459,7 +482,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow
|
|||||||
t.path.Wrapping = fyne.TextTruncate
|
t.path.Wrapping = fyne.TextTruncate
|
||||||
|
|
||||||
t.Content = container.New(tracklist.colLayout,
|
t.Content = container.New(tracklist.colLayout,
|
||||||
t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.plays, t.bitrate, t.size, t.path)
|
t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.rating, t.plays, t.bitrate, t.size, t.path)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,12 +569,15 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) {
|
|||||||
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng
|
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.rating.Rating = tr.UserRating
|
||||||
|
|
||||||
// Show only columns configured to be visible
|
// Show only columns configured to be visible
|
||||||
t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)]
|
t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)]
|
||||||
t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)]
|
t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)]
|
||||||
t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)]
|
t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)]
|
||||||
t.year.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnYear)]
|
t.year.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnYear)]
|
||||||
t.favorite.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnFavorite)]
|
t.favorite.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnFavorite)]
|
||||||
|
t.rating.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnRating)]
|
||||||
t.plays.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnPlays)]
|
t.plays.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnPlays)]
|
||||||
t.bitrate.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnBitrate)]
|
t.bitrate.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnBitrate)]
|
||||||
t.size.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnSize)]
|
t.size.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnSize)]
|
||||||
@@ -574,6 +600,10 @@ func (t *TrackRow) toggleFavorited() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TrackRow) setTrackRating(rating int) {
|
||||||
|
t.tracklist.onSetRating(t.trackID, rating)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TrackRow) TappedSecondary(e *fyne.PointEvent) {
|
func (t *TrackRow) TappedSecondary(e *fyne.PointEvent) {
|
||||||
if t.OnTappedSecondary != nil {
|
if t.OnTappedSecondary != nil {
|
||||||
t.OnTappedSecondary(e, t.trackIdx)
|
t.OnTappedSecondary(e, t.trackIdx)
|
||||||
|
|||||||
Reference in New Issue
Block a user