From c4dba8f1fb6f50c7c741ba0ea0f9c179638f2693 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 20 Feb 2023 17:14:56 -0800 Subject: [PATCH 01/13] beginning work on browse by favorite artists and songs --- ui/browsing/favoritespage.go | 36 ++++++++++++++----- ui/layouts/hboxcustompadding.go | 58 ++++++++++++++++++++++++++++++ ui/widgets/togglebuttongroup.go | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 ui/layouts/hboxcustompadding.go create mode 100644 ui/widgets/togglebuttongroup.go diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 9d015c1..6b040b2 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -1,7 +1,9 @@ package browsing import ( + "log" "supersonic/backend" + "supersonic/res" "supersonic/ui/widgets" "time" @@ -26,6 +28,7 @@ type FavoritesPage struct { searcher *widgets.Searcher searchText string titleDisp *widget.RichText + toggleBtns *widgets.ToggleButtonGroup container *fyne.Container } @@ -46,6 +49,7 @@ func NewFavoritesPage(sm *backend.ServerManager, pm *backend.PlaybackManager, lm a.grid.OnShowArtistPage = a.onShowArtistPage a.searcher = widgets.NewSearcher() a.searcher.OnSearched = a.OnSearched + a.createToggleButtons(0) a.createContainer(false) return a } @@ -57,6 +61,19 @@ func (a *FavoritesPage) createTitle() { } } +func (a *FavoritesPage) createToggleButtons(activeBtnIdx int) { + a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx, + widget.NewButtonWithIcon("", res.ResDiscInvertPng, func() { + log.Println("albums activated") + }), + widget.NewButtonWithIcon("", res.ResPeopleInvertPng, func() { + log.Println("artists activated") + }), + widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, func() { + log.Println("songs activated") + })) +} + func (a *FavoritesPage) createContainer(searchGrid bool) { searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer()) gr := a.grid @@ -64,7 +81,7 @@ func (a *FavoritesPage) createContainer(searchGrid bool) { gr = a.searchGrid } a.container = container.NewBorder( - container.NewHBox(widgets.NewHSpace(9), a.titleDisp, layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), + container.NewHBox(widgets.NewHSpace(9), a.titleDisp, container.NewCenter(a.toggleBtns), layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), nil, nil, nil, gr) } @@ -88,6 +105,7 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { if saved.searchText != "" { a.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState) } + a.createToggleButtons(saved.activeToggleBtn) a.createContainer(saved.searchText != "") return a @@ -107,13 +125,14 @@ func (a *FavoritesPage) Reload() { func (a *FavoritesPage) Save() SavedPage { sf := &savedFavoritesPage{ - pm: a.pm, - sm: a.sm, - im: a.im, - lm: a.lm, - nav: a.nav, - searchText: a.searchText, - gridState: a.grid.SaveToState(), + pm: a.pm, + sm: a.sm, + im: a.im, + lm: a.lm, + nav: a.nav, + searchText: a.searchText, + gridState: a.grid.SaveToState(), + activeToggleBtn: a.toggleBtns.ActivatedButtonIndex(), } if a.searchGrid != nil { sf.searchGridState = a.searchGrid.SaveToState() @@ -181,6 +200,7 @@ type savedFavoritesPage struct { gridState widgets.AlbumGridState searchGridState widgets.AlbumGridState searchText string + activeToggleBtn int nav func(Route) } diff --git a/ui/layouts/hboxcustompadding.go b/ui/layouts/hboxcustompadding.go new file mode 100644 index 0000000..cedac3c --- /dev/null +++ b/ui/layouts/hboxcustompadding.go @@ -0,0 +1,58 @@ +package layouts + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +var _ fyne.Layout = (*VboxCustomPadding)(nil) + +type HboxCustomPadding struct { + ExtraPad float32 + DisableThemePad bool +} + +func (v *HboxCustomPadding) MinSize(objects []fyne.CanvasObject) fyne.Size { + minSize := fyne.NewSize(0, 0) + for _, child := range objects { + if !child.Visible() { + continue + } + + minSize.Height = fyne.Max(child.MinSize().Height, minSize.Height) + minSize.Width += child.MinSize().Width + } + minSize.Width += (v.themePad() + v.ExtraPad) * float32(len(objects)-1) + return minSize +} + +func (v *HboxCustomPadding) Layout(objects []fyne.CanvasObject, size fyne.Size) { + total := float32(0) + for _, child := range objects { + if !child.Visible() { + continue + } + total += child.MinSize().Width + } + + x, y := float32(0), float32(0) + + extra := float32(0) + for _, child := range objects { + if !child.Visible() { + continue + } + width := child.MinSize().Width + child.Move(fyne.NewPos(x+extra, y)) + x += width + child.Resize(fyne.NewSize(width, size.Height)) + extra += (v.themePad() + v.ExtraPad) + } +} + +func (v *HboxCustomPadding) themePad() float32 { + if v.DisableThemePad { + return 0 + } + return theme.Padding() +} diff --git a/ui/widgets/togglebuttongroup.go b/ui/widgets/togglebuttongroup.go new file mode 100644 index 0000000..bdea9db --- /dev/null +++ b/ui/widgets/togglebuttongroup.go @@ -0,0 +1,64 @@ +package widgets + +import ( + "supersonic/ui/layouts" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +type ToggleButtonGroup struct { + widget.BaseWidget + + buttonContainer *fyne.Container + + activeBtnIdx int +} + +func NewToggleButtonGroup(activatedBtnIdx int, buttons ...*widget.Button) *ToggleButtonGroup { + t := &ToggleButtonGroup{} + t.ExtendBaseWidget(t) + t.buttonContainer = container.New(&layouts.HboxCustomPadding{DisableThemePad: true}) + for i, b := range buttons { + b.Importance = widget.MediumImportance + t.buttonContainer.Add(b) + prevOnTapped := b.OnTapped + b.OnTapped = func(i int) func() { + return func() { + if t.onTapped(i) && prevOnTapped != nil { + prevOnTapped() + } + } + }(i) + } + if activatedBtnIdx >= 0 && activatedBtnIdx <= len(buttons) { + buttons[activatedBtnIdx].Importance = widget.HighImportance + } + + return t +} + +func (t *ToggleButtonGroup) ActivatedButtonIndex() int { + return t.activeBtnIdx +} + +func (t *ToggleButtonGroup) onTapped(btnIdx int) bool { + for i, b := range t.buttonContainer.Objects { + if i == btnIdx { + b.(*widget.Button).Importance = widget.HighImportance + } else { + b.(*widget.Button).Importance = widget.MediumImportance + } + } + changed := t.activeBtnIdx != btnIdx + t.activeBtnIdx = btnIdx + if changed { + t.Refresh() + } + return changed +} + +func (t *ToggleButtonGroup) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(t.buttonContainer) +} From ae2ff2df2ffbe68479b314a66dea969461805eb7 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 20 Feb 2023 17:54:18 -0800 Subject: [PATCH 02/13] add favorite (aka starred) column to tracklist --- ui/widgets/auxcontrols.go | 32 +------------ ui/widgets/tappableicon.go | 34 ++++++++++++++ ui/widgets/tracklist.go | 94 +++++++++++++++++++++++++++----------- 3 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 ui/widgets/tappableicon.go diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 1168a8e..41ebc0a 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -3,7 +3,6 @@ package widgets import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -56,37 +55,10 @@ func (v *volumeSlider) MinSize() fyne.Size { return fyne.NewSize(v.Width, h) } -type tappableIcon struct { - widget.Icon - - OnTapped func() -} - -func newTappableIcon(res fyne.Resource) *tappableIcon { - icon := &tappableIcon{} - icon.ExtendBaseWidget(icon) - icon.SetResource(res) - - return icon -} - -func (t *tappableIcon) Tapped(_ *fyne.PointEvent) { - if t.OnTapped != nil { - t.OnTapped() - } -} - -func (t *tappableIcon) TappedSecondary(_ *fyne.PointEvent) { -} - -func (t *tappableIcon) Cursor() desktop.Cursor { - return desktop.PointerCursor -} - type VolumeControl struct { widget.BaseWidget - icon *tappableIcon + icon *TappableIcon slider *volumeSlider OnVolumeChanged func(int) @@ -100,7 +72,7 @@ type VolumeControl struct { func NewVolumeControl(initialVol int) *VolumeControl { v := &VolumeControl{} v.ExtendBaseWidget(v) - v.icon = newTappableIcon(theme.VolumeUpIcon()) + v.icon = NewTappbaleIcon(theme.VolumeUpIcon()) v.icon.OnTapped = v.toggleMute v.slider = NewVolumeSlider(100) v.lastVol = initialVol diff --git a/ui/widgets/tappableicon.go b/ui/widgets/tappableicon.go new file mode 100644 index 0000000..4ca70f3 --- /dev/null +++ b/ui/widgets/tappableicon.go @@ -0,0 +1,34 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/widget" +) + +type TappableIcon struct { + widget.Icon + + OnTapped func() +} + +func NewTappbaleIcon(res fyne.Resource) *TappableIcon { + icon := &TappableIcon{} + icon.ExtendBaseWidget(icon) + icon.SetResource(res) + + return icon +} + +func (t *TappableIcon) Tapped(_ *fyne.PointEvent) { + if t.OnTapped != nil { + t.OnTapped() + } +} + +func (t *TappableIcon) TappedSecondary(_ *fyne.PointEvent) { +} + +func (t *TappableIcon) Cursor() desktop.Cursor { + return desktop.PointerCursor +} diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index d85ed56..b70b075 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -4,6 +4,7 @@ import ( "log" "runtime" "strconv" + "supersonic/res" "supersonic/ui/layouts" "supersonic/ui/os" "supersonic/ui/util" @@ -19,12 +20,13 @@ import ( ) const ( - ColumnArtist = "Artist" - ColumnAlbum = "Album" - ColumnTime = "Time" - ColumnYear = "Year" - ColumnPlays = "Plays" - ColumnBitrate = "Bitrate" + ColumnArtist = "Artist" + ColumnAlbum = "Album" + ColumnTime = "Time" + ColumnYear = "Year" + ColumnFavorite = "Favorite" + ColumnPlays = "Plays" + ColumnBitrate = "Bitrate" ) type Tracklist struct { @@ -41,6 +43,7 @@ type Tracklist struct { OnPlaySelection func(tracks []*subsonic.Child) OnAddToQueue func(trackIDs []*subsonic.Child) OnAddToPlaylist func(trackIDs []string) + OnSetFavorite func(trackIDs []string, fav bool) visibleColumns []bool @@ -54,11 +57,12 @@ type Tracklist struct { } func NewTracklist(tracks []*subsonic.Child) *Tracklist { - t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1, visibleColumns: make([]bool, 8)} + t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1, visibleColumns: make([]bool, 9)} t.ExtendBaseWidget(t) t.selectionMgr = util.NewListSelectionManager(func() int { return len(t.Tracks) }) - t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, -1, 60, 60, 65, 75}) + // #, Title, Artist, Album, Time, Year, Favorite, Plays, Bitrate + t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, -1, 60, 60, 47, 65, 75}) t.buildHeader() t.hdr.OnColumnVisibilityChanged = t.setColumnVisible playingIcon := container.NewCenter(container.NewHBox(NewHSpace(2), widget.NewIcon(theme.MediaPlayIcon()))) @@ -93,6 +97,7 @@ func (t *Tracklist) buildHeader() { {Text: "Album", AlignTrailing: false, CanToggleVisible: true}, {Text: "Time", AlignTrailing: true, CanToggleVisible: true}, {Text: "Year", AlignTrailing: true, CanToggleVisible: true}, + {Text: "Fav.", AlignTrailing: false, CanToggleVisible: true}, {Text: "Plays", AlignTrailing: true, CanToggleVisible: true}, {Text: "Bitrate", AlignTrailing: true, CanToggleVisible: true}}, t.colLayout) @@ -239,6 +244,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition) } +func (t *Tracklist) onSetFavorite(trackID string, fav bool) { + if t.OnSetFavorite != nil { + t.OnSetFavorite([]string{trackID}, fav) + } +} + func (t *Tracklist) selectedTracks() []*subsonic.Child { sel := t.selectionMgr.GetSelection() tracks := make([]*subsonic.Child, 0, len(sel)) @@ -272,10 +283,12 @@ func ColNumber(colName string) int { return 4 case ColumnYear: return 5 - case ColumnPlays: + case ColumnFavorite: return 6 - case ColumnBitrate: + case ColumnPlays: return 7 + case ColumnBitrate: + return 8 default: log.Printf("error: Tracklist: invalid column name %s", colName) return -100 @@ -294,8 +307,10 @@ func colName(i int) string { case 5: return ColumnYear case 6: - return ColumnPlays + return ColumnFavorite case 7: + return ColumnPlays + case 8: return ColumnBitrate default: return "" @@ -306,21 +321,23 @@ type TrackRow struct { widget.BaseWidget // internal state - tracklist *Tracklist - trackIdx int - trackID string - isPlaying bool - playCount int64 - tappedAt int64 // unixMillis + tracklist *Tracklist + trackIdx int + trackID string + isPlaying bool + isFavorite bool + playCount int64 + tappedAt int64 // unixMillis - num *widget.RichText - name *widget.RichText - artist *widget.RichText - album *widget.RichText - dur *widget.RichText - year *widget.RichText - bitrate *widget.RichText - plays *widget.RichText + num *widget.RichText + name *widget.RichText + artist *widget.RichText + album *widget.RichText + dur *widget.RichText + year *widget.RichText + favorite *fyne.Container + bitrate *widget.RichText + plays *widget.RichText OnTapped func() OnDoubleTapped func() @@ -346,6 +363,9 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.dur.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing t.year = widget.NewRichTextWithText("") t.year.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing + favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng) + favorite.OnTapped = t.toggleFavorited + t.favorite = container.NewCenter(favorite) t.plays = widget.NewRichTextWithText("") t.plays.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing t.bitrate = widget.NewRichTextWithText("") @@ -355,7 +375,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.selectionRect.Hidden = true t.container = container.NewMax(t.selectionRect, container.New(tracklist.colLayout, - t.num, t.name, t.artist, t.album, t.dur, t.year, t.plays, t.bitrate)) + t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.plays, t.bitrate)) return t } @@ -374,6 +394,7 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { t.album.Segments[0].(*widget.TextSegment).Text = tr.Album t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) t.year.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.Year) + t.plays.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(int(tr.PlayCount)) t.bitrate.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.BitRate) @@ -392,16 +413,37 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { } } + if tr.Starred.IsZero() && t.isFavorite { + t.isFavorite = false + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + } else if !t.isFavorite { + t.isFavorite = true + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + } + t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)] t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)] t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)] t.year.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnYear)] + t.favorite.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnFavorite)] t.plays.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnPlays)] t.bitrate.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnBitrate)] t.Refresh() } +func (t *TrackRow) toggleFavorited() { + if t.isFavorite { + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + t.favorite.Refresh() + t.tracklist.onSetFavorite(t.trackID, false) + } else { + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + t.favorite.Refresh() + t.tracklist.onSetFavorite(t.trackID, true) + } +} + func (t *TrackRow) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(t.container) } From 9314ba6d80ca379cc2e210f60d5d2ffe1e89048d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 20 Feb 2023 20:45:16 -0800 Subject: [PATCH 03/13] extract duplicate code in favorites page --- ui/browsing/favoritespage.go | 37 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 6b040b2..0359154 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -41,27 +41,18 @@ func NewFavoritesPage(sm *backend.ServerManager, pm *backend.PlaybackManager, lm nav: nav, } a.ExtendBaseWidget(a) - a.createTitle() - iter := lm.StarredIter() - a.grid = widgets.NewAlbumGrid(iter, im, false) - a.grid.OnPlayAlbum = a.onPlayAlbum - a.grid.OnShowAlbumPage = a.onShowAlbumPage - a.grid.OnShowArtistPage = a.onShowArtistPage - a.searcher = widgets.NewSearcher() - a.searcher.OnSearched = a.OnSearched - a.createToggleButtons(0) + a.createHeader(0, "") + a.grid = widgets.NewAlbumGrid(a.lm.StarredIter(), a.im, false) + a.connectGridActions() a.createContainer(false) return a } -func (a *FavoritesPage) createTitle() { +func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) { a.titleDisp = widget.NewRichTextWithText("Favorites") a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, } -} - -func (a *FavoritesPage) createToggleButtons(activeBtnIdx int) { a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx, widget.NewButtonWithIcon("", res.ResDiscInvertPng, func() { log.Println("albums activated") @@ -72,6 +63,15 @@ func (a *FavoritesPage) createToggleButtons(activeBtnIdx int) { widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, func() { log.Println("songs activated") })) + a.searcher = widgets.NewSearcher() + a.searcher.OnSearched = a.OnSearched + a.searcher.Entry.Text = searchText +} + +func (a *FavoritesPage) connectGridActions() { + a.grid.OnPlayAlbum = a.onPlayAlbum + a.grid.OnShowAlbumPage = a.onShowAlbumPage + a.grid.OnShowArtistPage = a.onShowArtistPage } func (a *FavoritesPage) createContainer(searchGrid bool) { @@ -94,18 +94,13 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { nav: saved.nav, } a.ExtendBaseWidget(a) - a.createTitle() + a.createHeader(saved.activeToggleBtn, saved.searchText) a.grid = widgets.NewAlbumGridFromState(saved.gridState) - a.grid.OnPlayAlbum = a.onPlayAlbum - a.grid.OnShowAlbumPage = a.onShowAlbumPage - a.grid.OnShowArtistPage = a.onShowArtistPage - a.searcher = widgets.NewSearcher() - a.searcher.OnSearched = a.OnSearched - a.searcher.Entry.Text = saved.searchText + a.connectGridActions() + if saved.searchText != "" { a.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState) } - a.createToggleButtons(saved.activeToggleBtn) a.createContainer(saved.searchText != "") return a From fd07c18fa977855179f335da6ed7d00bf89ece5e Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 20 Feb 2023 21:24:59 -0800 Subject: [PATCH 04/13] add list views for favorite artists and songs --- ui/browsing/favoritespage.go | 156 +++++++++++++++++++++++++++-------- ui/browsing/router.go | 2 +- 2 files changed, 122 insertions(+), 36 deletions(-) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 0359154..65db18a 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -4,6 +4,8 @@ import ( "log" "supersonic/backend" "supersonic/res" + "supersonic/ui/controller" + "supersonic/ui/layouts" "supersonic/ui/widgets" "time" @@ -18,27 +20,31 @@ import ( type FavoritesPage struct { widget.BaseWidget - pm *backend.PlaybackManager - im *backend.ImageManager - sm *backend.ServerManager - lm *backend.LibraryManager - nav func(Route) - grid *widgets.AlbumGrid - searchGrid *widgets.AlbumGrid - searcher *widgets.Searcher - searchText string - titleDisp *widget.RichText - toggleBtns *widgets.ToggleButtonGroup - container *fyne.Container + contr controller.Controller + pm *backend.PlaybackManager + im *backend.ImageManager + sm *backend.ServerManager + lm *backend.LibraryManager + nav func(Route) + grid *widgets.AlbumGrid + searchGrid *widgets.AlbumGrid + artistListCtr *fyne.Container + tracklistCtr *fyne.Container + searcher *widgets.Searcher + searchText string + titleDisp *widget.RichText + toggleBtns *widgets.ToggleButtonGroup + container *fyne.Container } -func NewFavoritesPage(sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { +func NewFavoritesPage(contr controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { a := &FavoritesPage{ - pm: pm, - lm: lm, - sm: sm, - im: im, - nav: nav, + contr: contr, + pm: pm, + lm: lm, + sm: sm, + im: im, + nav: nav, } a.ExtendBaseWidget(a) a.createHeader(0, "") @@ -54,15 +60,9 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) { SizeName: theme.SizeNameHeadingText, } a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx, - widget.NewButtonWithIcon("", res.ResDiscInvertPng, func() { - log.Println("albums activated") - }), - widget.NewButtonWithIcon("", res.ResPeopleInvertPng, func() { - log.Println("artists activated") - }), - widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, func() { - log.Println("songs activated") - })) + widget.NewButtonWithIcon("", res.ResDiscInvertPng, a.onShowFavoriteAlbums), + widget.NewButtonWithIcon("", res.ResPeopleInvertPng, a.onShowFavoriteArtists), + widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, a.onShowFavoriteSongs)) a.searcher = widgets.NewSearcher() a.searcher.OnSearched = a.OnSearched a.searcher.Entry.Text = searchText @@ -87,11 +87,12 @@ func (a *FavoritesPage) createContainer(searchGrid bool) { func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { a := &FavoritesPage{ - pm: saved.pm, - lm: saved.lm, - sm: saved.sm, - im: saved.im, - nav: saved.nav, + contr: saved.contr, + pm: saved.pm, + lm: saved.lm, + sm: saved.sm, + im: saved.im, + nav: saved.nav, } a.ExtendBaseWidget(a) a.createHeader(saved.activeToggleBtn, saved.searchText) @@ -102,6 +103,11 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { a.searchGrid = widgets.NewAlbumGridFromState(saved.searchGridState) } a.createContainer(saved.searchText != "") + if saved.activeToggleBtn == 1 { + a.onShowFavoriteArtists() + } else if saved.activeToggleBtn == 2 { + a.onShowFavoriteSongs() + } return a } @@ -112,7 +118,7 @@ func (a *FavoritesPage) Route() Route { func (a *FavoritesPage) Reload() { if a.searchText != "" { - a.doSearch(a.searchText) + a.doSearchAlbums(a.searchText) } else { a.grid.Reset(a.lm.StarredIter()) } @@ -120,6 +126,7 @@ func (a *FavoritesPage) Reload() { func (a *FavoritesPage) Save() SavedPage { sf := &savedFavoritesPage{ + contr: a.contr, pm: a.pm, sm: a.sm, im: a.im, @@ -151,10 +158,10 @@ func (a *FavoritesPage) OnSearched(query string) { a.Refresh() return } - a.doSearch(query) + a.doSearchAlbums(query) } -func (a *FavoritesPage) doSearch(query string) { +func (a *FavoritesPage) doSearchAlbums(query string) { iter := a.lm.SearchIterWithFilter(query, func(al *subsonic.AlbumID3) bool { return al.Starred.After(time.Time{}) }) @@ -170,6 +177,84 @@ func (a *FavoritesPage) doSearch(query string) { a.Refresh() } +func (a *FavoritesPage) onShowFavoriteAlbums() { + a.searcher.Entry.Show() + if a.searchText == "" { + a.container.Objects[0] = a.grid + } else { + a.container.Objects[0] = a.searchGrid + } + a.Refresh() +} + +func (a *FavoritesPage) onShowFavoriteArtists() { + a.searcher.Entry.Hide() // disable search on artists for now + if a.artistListCtr == nil { + go func() { + s, err := a.sm.Server.GetStarred2(nil) + if err != nil { + log.Println("error getting starred items: %s", err.Error()) + return + } + model := make([]widgets.ArtistGenrePlaylistItemModel, 0) + for _, ar := range s.Artist { + model = append(model, widgets.ArtistGenrePlaylistItemModel{ + Name: ar.Name, + AlbumCount: ar.AlbumCount, + }) + } + artistList := widgets.NewArtistGenrePlaylist(model) + artistList.ShowAlbumCount = true + artistList.OnNavTo = func(artistID string) { + a.nav(ArtistRoute(artistID)) + } + a.artistListCtr = container.New( + &layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, + artistList) + a.container.Objects[0] = a.artistListCtr + a.Refresh() + }() + } else { + a.container.Objects[0] = a.artistListCtr + a.Refresh() + } +} + +func (a *FavoritesPage) onShowFavoriteSongs() { + a.searcher.Entry.Hide() // disable search on songs for now + if a.tracklistCtr == nil { + go func() { + s, err := a.sm.Server.GetStarred2(nil) + if err != nil { + log.Println("error getting starred items: %s", err.Error()) + return + } + tracklist := widgets.NewTracklist(s.Song) + // TODO: get visible columns from config + tracklist.SetVisibleColumns([]string{"Artist", "Album", "Plays"}) + // connect tracklist actions + tracklist.OnPlayTrackAt = func(idx int) { + a.pm.LoadTracks(tracklist.Tracks, false, false) + a.pm.PlayTrackAt(idx) + } + tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { a.pm.LoadTracks(tracks, true, false) } + tracklist.OnPlaySelection = func(tracks []*subsonic.Child) { + a.pm.LoadTracks(tracks, false, false) + a.pm.PlayFromBeginning() + } + tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.tracklistCtr = container.New( + &layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, + tracklist) + a.container.Objects[0] = a.tracklistCtr + a.Refresh() + }() + } else { + a.container.Objects[0] = a.tracklistCtr + a.Refresh() + } +} + func (a *FavoritesPage) onPlayAlbum(albumID string) { go a.pm.PlayAlbum(albumID, 0) } @@ -188,6 +273,7 @@ func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer { } type savedFavoritesPage struct { + contr controller.Controller pm *backend.PlaybackManager sm *backend.ServerManager im *backend.ImageManager diff --git a/ui/browsing/router.go b/ui/browsing/router.go index d9a5c6b..a73079b 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -95,7 +95,7 @@ func (r Router) CreatePage(rte Route) Page { case Artists: return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute) case Favorites: - return NewFavoritesPage(r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + return NewFavoritesPage(r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genre: return NewGenrePage(rte.Arg, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genres: From 3de51ad48af7eb2ea1bd510cbcfb435893fa0f23 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 21 Feb 2023 08:33:40 -0800 Subject: [PATCH 05/13] refactor: add controller.ConnectTracklistActions --- ui/browsing/albumpage.go | 16 ++-------------- ui/browsing/favoritespage.go | 14 ++------------ ui/browsing/playlistpage.go | 16 +++------------- ui/controller/controller.go | 17 +++++++++++++++++ 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index c5a58f9..df0152f 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -66,14 +66,7 @@ func NewAlbumPage( a.header = NewAlbumPageHeader(a) a.tracklist = widgets.NewTracklist(nil) a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns) - // connect tracklist actions - a.tracklist.OnPlayTrackAt = a.onPlayTrackAt - a.tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { a.pm.LoadTracks(tracks, true, false) } - a.tracklist.OnPlaySelection = func(tracks []*subsonic.Child) { - a.pm.LoadTracks(tracks, false, false) - a.pm.PlayFromBeginning() - } - a.tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.contr.ConnectTracklistActions(a.tracklist) a.container = container.NewBorder( container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 15, PadBottom: 10}, a.header), @@ -122,11 +115,6 @@ func (a *AlbumPage) SelectAll() { a.tracklist.SelectAll() } -func (a *AlbumPage) onPlayTrackAt(tracknum int) { - a.pm.LoadTracks(a.tracklist.Tracks, false, false) - a.pm.PlayTrackAt(tracknum) -} - // should be called asynchronously func (a *AlbumPage) load() { album, err := a.lm.GetAlbum(a.albumID) @@ -185,7 +173,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { } a.miscLabel = widget.NewLabel("") playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { - page.onPlayTrackAt(0) + go page.pm.PlayAlbum(page.albumID, 0) }) shuffleBtn := widget.NewButtonWithIcon(" Shuffle", res.ResShuffleInvertSvg, func() { page.pm.LoadTracks(page.tracklist.Tracks, false, true) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 65db18a..64d8949 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -226,23 +226,13 @@ func (a *FavoritesPage) onShowFavoriteSongs() { go func() { s, err := a.sm.Server.GetStarred2(nil) if err != nil { - log.Println("error getting starred items: %s", err.Error()) + log.Printf("error getting starred items: %s", err.Error()) return } tracklist := widgets.NewTracklist(s.Song) // TODO: get visible columns from config tracklist.SetVisibleColumns([]string{"Artist", "Album", "Plays"}) - // connect tracklist actions - tracklist.OnPlayTrackAt = func(idx int) { - a.pm.LoadTracks(tracklist.Tracks, false, false) - a.pm.PlayTrackAt(idx) - } - tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { a.pm.LoadTracks(tracks, true, false) } - tracklist.OnPlaySelection = func(tracks []*subsonic.Child) { - a.pm.LoadTracks(tracks, false, false) - a.pm.PlayFromBeginning() - } - tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.contr.ConnectTracklistActions(tracklist) a.tracklistCtr = container.New( &layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, tracklist) diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 56ef385..f1daef5 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -57,13 +57,7 @@ func NewPlaylistPage( fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist), } // connect tracklist actions - a.tracklist.OnPlayTrackAt = a.onPlayTrackAt - a.tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { a.pm.LoadTracks(tracks, true, false) } - a.tracklist.OnPlaySelection = func(tracks []*subsonic.Child) { - a.pm.LoadTracks(tracks, false, false) - a.pm.PlayFromBeginning() - } - a.tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.contr.ConnectTracklistActions(a.tracklist) a.container = container.NewBorder( container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 15, PadBottom: 10}, a.header), @@ -108,11 +102,6 @@ func (a *PlaylistPage) SelectAll() { a.tracklist.SelectAll() } -func (a *PlaylistPage) onPlayTrackAt(tracknum int) { - a.pm.LoadTracks(a.tracklist.Tracks, false, false) - a.pm.PlayTrackAt(tracknum) -} - // should be called asynchronously func (a *PlaylistPage) load() { playlist, err := a.sm.Server.GetPlaylist(a.playlistID) @@ -163,7 +152,8 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { a.createdAtLabel = widget.NewLabel("") a.trackTimeLabel = widget.NewLabel("") playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { - page.onPlayTrackAt(0) + page.pm.LoadTracks(page.tracklist.Tracks, false, false) + page.pm.PlayFromBeginning() }) // TODO: find way to pad shuffle svg rather than using a space in the label string shuffleBtn := widget.NewButtonWithIcon(" Shuffle", res.ResShuffleInvertSvg, func() { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index c226ad7..7a9bcdb 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -6,12 +6,14 @@ import ( "supersonic/backend" "supersonic/ui/dialogs" "supersonic/ui/util" + "supersonic/ui/widgets" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" + "github.com/dweymouth/go-subsonic/subsonic" ) type Controller struct { @@ -40,6 +42,21 @@ func (m Controller) ShowPopUpImage(img image.Image) { )) } +func (m Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { + tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow + tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { + m.App.PlaybackManager.LoadTracks(tracks, true, false) + } + tracklist.OnPlayTrackAt = func(idx int) { + m.App.PlaybackManager.LoadTracks(tracklist.Tracks, false, false) + m.App.PlaybackManager.PlayTrackAt(idx) + } + tracklist.OnPlaySelection = func(tracks []*subsonic.Child) { + m.App.PlaybackManager.LoadTracks(tracks, false, false) + m.App.PlaybackManager.PlayFromBeginning() + } +} + func (m Controller) PromptForFirstServer() { d := dialogs.NewAddEditServerDialog("Connect to Server", nil) pop := widget.NewModalPopUp(d, m.MainWindow.Canvas()) From 76380ddeae6a7d5d65963ca45e39bc4f6aa1ee62 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 21 Feb 2023 08:56:18 -0800 Subject: [PATCH 06/13] favorite songs view auto-number and show now playing track --- ui/browsing/favoritespage.go | 31 ++++++++++++++++++++++++------- ui/widgets/tracklist.go | 33 ++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 64d8949..8124940 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -20,18 +20,21 @@ import ( type FavoritesPage struct { widget.BaseWidget - contr controller.Controller - pm *backend.PlaybackManager - im *backend.ImageManager - sm *backend.ServerManager - lm *backend.LibraryManager - nav func(Route) + contr controller.Controller + pm *backend.PlaybackManager + im *backend.ImageManager + sm *backend.ServerManager + lm *backend.LibraryManager + nav func(Route) + + searchText string + nowPlayingID string + grid *widgets.AlbumGrid searchGrid *widgets.AlbumGrid artistListCtr *fyne.Container tracklistCtr *fyne.Container searcher *widgets.Searcher - searchText string titleDisp *widget.RichText toggleBtns *widgets.ToggleButtonGroup container *fyne.Container @@ -161,6 +164,18 @@ func (a *FavoritesPage) OnSearched(query string) { a.doSearchAlbums(query) } +var _ CanShowNowPlaying = (*FavoritesPage)(nil) + +func (a *FavoritesPage) OnSongChange(song *subsonic.Child) { + a.nowPlayingID = "" + if song != nil { + a.nowPlayingID = song.ID + } + if a.tracklistCtr != nil { + a.tracklistCtr.Objects[0].(*widgets.Tracklist).SetNowPlaying(a.nowPlayingID) + } +} + func (a *FavoritesPage) doSearchAlbums(query string) { iter := a.lm.SearchIterWithFilter(query, func(al *subsonic.AlbumID3) bool { return al.Starred.After(time.Time{}) @@ -230,8 +245,10 @@ func (a *FavoritesPage) onShowFavoriteSongs() { return } tracklist := widgets.NewTracklist(s.Song) + tracklist.AutoNumber = true // TODO: get visible columns from config tracklist.SetVisibleColumns([]string{"Artist", "Album", "Plays"}) + tracklist.SetNowPlaying(a.nowPlayingID) a.contr.ConnectTracklistActions(tracklist) a.tracklistCtr = container.New( &layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index b70b075..8f76c5a 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -323,6 +323,7 @@ type TrackRow struct { // internal state tracklist *Tracklist trackIdx int + trackNum int trackID string isPlaying bool isFavorite bool @@ -380,24 +381,40 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow } func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { - if tr.ID != t.trackID || isPlaying != t.isPlaying || tr.PlayCount != t.playCount { - t.isPlaying = isPlaying + // Update info that can change if this row is bound to + // a new track (*subsonic.Child) + if tr.ID != t.trackID { t.trackID = tr.ID t.playCount = tr.PlayCount - if rowNum < 0 { - rowNum = tr.Track - } - t.num.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(rowNum) t.name.Segments[0].(*widget.TextSegment).Text = tr.Title t.artist.Segments[0].(*widget.TextSegment).Text = tr.Artist t.album.Segments[0].(*widget.TextSegment).Text = tr.Album t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) t.year.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.Year) - t.plays.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(int(tr.PlayCount)) t.bitrate.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.BitRate) + } + // Update track num if needed + // (which can change based on bound *subsonic.Child or tracklist.AutoNumber) + if t.trackNum != rowNum { + if rowNum < 0 { + rowNum = tr.Track + } + t.trackNum = rowNum + t.num.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(rowNum) + } + + // Update play count if needed + if tr.PlayCount != t.playCount { + t.playCount = tr.PlayCount + t.plays.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(int(tr.PlayCount)) + } + + // Render whether track is playing or not + if isPlaying != t.isPlaying { + t.isPlaying = isPlaying t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying t.artist.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying t.album.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying @@ -413,6 +430,7 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { } } + // Render favorite column if tr.Starred.IsZero() && t.isFavorite { t.isFavorite = false t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng @@ -421,6 +439,7 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng } + // Show only columns configured to be visible t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)] t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)] t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)] From 4de0f9d5db77dbef2b58c32ef5eacf30763ea1fa Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 21 Feb 2023 09:16:13 -0800 Subject: [PATCH 07/13] favorites page - only allow one pending view change --- ui/browsing/favoritespage.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 8124940..e7f0490 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -27,8 +27,9 @@ type FavoritesPage struct { lm *backend.LibraryManager nav func(Route) - searchText string - nowPlayingID string + searchText string + nowPlayingID string + pendingViewSwitch bool grid *widgets.AlbumGrid searchGrid *widgets.AlbumGrid @@ -205,10 +206,14 @@ func (a *FavoritesPage) onShowFavoriteAlbums() { func (a *FavoritesPage) onShowFavoriteArtists() { a.searcher.Entry.Hide() // disable search on artists for now if a.artistListCtr == nil { + if a.pendingViewSwitch { + return + } + a.pendingViewSwitch = true go func() { s, err := a.sm.Server.GetStarred2(nil) if err != nil { - log.Println("error getting starred items: %s", err.Error()) + log.Printf("error getting starred items: %s", err.Error()) return } model := make([]widgets.ArtistGenrePlaylistItemModel, 0) @@ -228,6 +233,7 @@ func (a *FavoritesPage) onShowFavoriteArtists() { artistList) a.container.Objects[0] = a.artistListCtr a.Refresh() + a.pendingViewSwitch = false }() } else { a.container.Objects[0] = a.artistListCtr @@ -238,6 +244,10 @@ func (a *FavoritesPage) onShowFavoriteArtists() { func (a *FavoritesPage) onShowFavoriteSongs() { a.searcher.Entry.Hide() // disable search on songs for now if a.tracklistCtr == nil { + if a.pendingViewSwitch { + return + } + a.pendingViewSwitch = true go func() { s, err := a.sm.Server.GetStarred2(nil) if err != nil { @@ -255,6 +265,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() { tracklist) a.container.Objects[0] = a.tracklistCtr a.Refresh() + a.pendingViewSwitch = false }() } else { a.container.Objects[0] = a.tracklistCtr From 3bf8266e6759535d7e5fd5ca6a656cbf39ea003d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 21 Feb 2023 18:18:30 -0800 Subject: [PATCH 08/13] add favorite button to artist page --- ui/browsing/artistpage.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 3f2d119..b1c291c 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -124,6 +124,7 @@ type ArtistPageHeader struct { titleDisp *widget.RichText biographyDisp *widget.RichText similarArtists *fyne.Container + favoriteBtn *widgets.FavoriteButton container *fyne.Container } @@ -139,6 +140,7 @@ func NewArtistPageHeader(page *ArtistPage, nav func(Route)) *ArtistPageHeader { SizeName: theme.SizeNameHeadingText, } a.artistImage = widgets.NewImagePlaceholder(res.ResPeopleInvertPng, 225) + a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() }) a.biographyDisp.Wrapping = fyne.TextWrapWord a.ExtendBaseWidget(a) a.createContainer() @@ -149,6 +151,7 @@ func (a *ArtistPageHeader) Update(artist *subsonic.ArtistID3) { if artist == nil { return } + a.favoriteBtn.IsFavorited = !artist.Starred.IsZero() a.artistID = artist.ID a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name a.titleDisp.Refresh() @@ -197,9 +200,20 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) { } } +func (a *ArtistPageHeader) toggleFavorited() { + if a.favoriteBtn.IsFavorited { + a.artistPage.sm.Server.Star(subsonic.StarParameters{ArtistIDs: []string{a.artistID}}) + } else { + a.artistPage.sm.Server.Unstar(subsonic.StarParameters{ArtistIDs: []string{a.artistID}}) + } +} + func (a *ArtistPageHeader) createContainer() { a.container = container.NewBorder(nil, nil, a.artistImage, nil, - container.NewBorder(a.titleDisp, nil, nil, nil, container.NewVBox(a.biographyDisp, a.similarArtists))) + container.NewVBox( + container.New(&layouts.VboxCustomPadding{ExtraPad: -10}, + a.titleDisp, a.biographyDisp, a.similarArtists), + container.NewHBox(widgets.NewHSpace(2), a.favoriteBtn))) } func (a *ArtistPageHeader) CreateRenderer() fyne.WidgetRenderer { From 4cef01f1ca26f24a9fb2eeb7f7ca50d238441981 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 21 Feb 2023 18:20:20 -0800 Subject: [PATCH 09/13] hook up favorite button in tracklist; reload all 3 views in favorites page --- ui/browsing/favoritespage.go | 51 ++++++++++++++++++++++++++++++------ ui/controller/controller.go | 8 ++++++ ui/widgets/tracklist.go | 15 ++++++++--- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index e7f0490..1236612 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -121,11 +121,40 @@ func (a *FavoritesPage) Route() Route { } func (a *FavoritesPage) Reload() { + // reload favorite albums view if a.searchText != "" { a.doSearchAlbums(a.searchText) } else { a.grid.Reset(a.lm.StarredIter()) } + if a.tracklistCtr != nil || a.artistListCtr != nil { + go func() { + // re-fetch starred info from server + starred, err := a.sm.Server.GetStarred2(nil) + if err != nil { + log.Printf("error getting starred items: %s", err.Error()) + return + } + if a.tracklistCtr != nil { + // refresh favorite songs view + tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist) + tr.Tracks = starred.Song + if a.toggleBtns.ActivatedButtonIndex() == 2 { + // favorite songs view is visible + tr.Refresh() + } + } + if a.artistListCtr != nil { + // refresh favorite artists view + al := a.artistListCtr.Objects[0].(*widgets.ArtistGenrePlaylist) + al.Items = buildArtistListModel(starred.Artist) + if a.toggleBtns.ActivatedButtonIndex() == 1 { + // favorite artists view is visible + al.Refresh() + } + } + }() + } } func (a *FavoritesPage) Save() SavedPage { @@ -167,7 +196,7 @@ func (a *FavoritesPage) OnSearched(query string) { var _ CanShowNowPlaying = (*FavoritesPage)(nil) -func (a *FavoritesPage) OnSongChange(song *subsonic.Child) { +func (a *FavoritesPage) OnSongChange(song *subsonic.Child, _ *subsonic.Child) { a.nowPlayingID = "" if song != nil { a.nowPlayingID = song.ID @@ -216,13 +245,7 @@ func (a *FavoritesPage) onShowFavoriteArtists() { log.Printf("error getting starred items: %s", err.Error()) return } - model := make([]widgets.ArtistGenrePlaylistItemModel, 0) - for _, ar := range s.Artist { - model = append(model, widgets.ArtistGenrePlaylistItemModel{ - Name: ar.Name, - AlbumCount: ar.AlbumCount, - }) - } + model := buildArtistListModel(s.Artist) artistList := widgets.NewArtistGenrePlaylist(model) artistList.ShowAlbumCount = true artistList.OnNavTo = func(artistID string) { @@ -241,6 +264,18 @@ func (a *FavoritesPage) onShowFavoriteArtists() { } } +func buildArtistListModel(artists []*subsonic.ArtistID3) []widgets.ArtistGenrePlaylistItemModel { + model := make([]widgets.ArtistGenrePlaylistItemModel, 0) + for _, ar := range artists { + model = append(model, widgets.ArtistGenrePlaylistItemModel{ + ID: ar.ID, + Name: ar.Name, + AlbumCount: ar.AlbumCount, + }) + } + return model +} + func (a *FavoritesPage) onShowFavoriteSongs() { a.searcher.Entry.Hide() // disable search on songs for now if a.tracklistCtr == nil { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 7a9bcdb..270dee0 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -55,6 +55,14 @@ func (m Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { m.App.PlaybackManager.LoadTracks(tracks, false, false) m.App.PlaybackManager.PlayFromBeginning() } + tracklist.OnSetFavorite = func(trackIDs []string, fav bool) { + s := m.App.ServerManager.Server + if fav { + go s.Star(subsonic.StarParameters{SongIDs: trackIDs}) + } else { + go s.Unstar(subsonic.StarParameters{SongIDs: trackIDs}) + } + } } func (m Controller) PromptForFirstServer() { diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 8f76c5a..cb05c0a 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -147,7 +147,7 @@ func (t *Tracklist) SetNowPlaying(trackID string) { break } } - t.list.Refresh() + t.Refresh() } func (t *Tracklist) IncrementPlayCount(track *subsonic.Child) { @@ -385,7 +385,6 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { // a new track (*subsonic.Child) if tr.ID != t.trackID { t.trackID = tr.ID - t.playCount = tr.PlayCount t.name.Segments[0].(*widget.TextSegment).Text = tr.Title t.artist.Segments[0].(*widget.TextSegment).Text = tr.Artist @@ -431,10 +430,16 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { } // Render favorite column - if tr.Starred.IsZero() && t.isFavorite { + // TODO: right now the only way for the favorite status to change while the tracklist is visible + // is by the user clicking on the heart icon in the favorites column + // If this changes in the future (e.g. context menu entry on tracklist), then we will + // need better state management/onChanged notif so we know to re-render the column + // (maybe update the Starred field directly on the track struct and issue a Refresh call - + // like we do to update the now playing value when scrobbles happen) + if tr.Starred.IsZero() { t.isFavorite = false t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng - } else if !t.isFavorite { + } else { t.isFavorite = true t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng } @@ -455,10 +460,12 @@ func (t *TrackRow) toggleFavorited() { if t.isFavorite { t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng t.favorite.Refresh() + t.isFavorite = false t.tracklist.onSetFavorite(t.trackID, false) } else { t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng t.favorite.Refresh() + t.isFavorite = true t.tracklist.onSetFavorite(t.trackID, true) } } From d25baa9869e4847b732c23d34a5b9872a23c6d78 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 22 Feb 2023 18:42:16 -0800 Subject: [PATCH 10/13] ensure play queue track state is updated on favorite/unfavorite and scrobbles --- backend/playbackmanager.go | 24 ++++++++++++++++++++++-- sharedutil/sharedutil.go | 19 +++++++++++++++++++ ui/browsing/albumpage.go | 3 ++- ui/browsing/nowplayingpage.go | 6 ++++-- ui/browsing/playlistpage.go | 3 ++- ui/controller/controller.go | 3 +++ ui/widgets/tracklist.go | 23 +++++++++++++---------- 7 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 sharedutil/sharedutil.go diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index c105631..f693bec 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -6,6 +6,7 @@ import ( "strconv" "supersonic/backend/util" "supersonic/player" + "supersonic/sharedutil" "time" "github.com/dweymouth/go-subsonic/subsonic" @@ -141,7 +142,11 @@ func (p *PlaybackManager) LoadTracks(tracks []*subsonic.Child, appendToQueue, sh return err } p.player.AppendFile(url.String()) - p.playQueue = append(p.playQueue, tracks[i]) + // ensure a deep copy of the track info so that we can maintain our own state + // (tracking play count increases, favorite, and rating) without messing up + // other views' track models + tr := *tracks[i] + p.playQueue = append(p.playQueue, &tr) } return nil } @@ -176,10 +181,24 @@ func (p *PlaybackManager) PlayTrackAt(idx int) error { func (p *PlaybackManager) GetPlayQueue() []*subsonic.Child { pq := make([]*subsonic.Child, len(p.playQueue)) - copy(pq, p.playQueue) + for i, tr := range p.playQueue { + copy := *tr + pq[i] = © + } return pq } +// Any time the user changes the favorite status of a track elsewhere in the app, +// this should be called to ensure the in-memory track model is updated. +func (p *PlaybackManager) OnTrackFavoriteStatusChanged(id string, fav bool) { + tr := sharedutil.FindTrackByID(id, p.playQueue) + if fav { + tr.Starred = time.Now() + } else { + tr.Starred = time.Time{} + } +} + // trackIdxs must be sorted func (p *PlaybackManager) RemoveTracksFromQueue(trackIdxs []int) { newQueue := make([]*subsonic.Child, 0, len(p.playQueue)-len(trackIdxs)) @@ -228,6 +247,7 @@ func (p *PlaybackManager) checkScrobble(playDur time.Duration) { song := p.playQueue[p.nowPlayingIdx] if playDur.Seconds()/p.curTrackTime > ScrobbleThreshold { log.Printf("Scrobbling %q", song.Title) + song.PlayCount += 1 p.lastScrobbled = song p.sm.Server.Scrobble(song.ID, map[string]string{"time": strconv.FormatInt(time.Now().Unix()*1000, 10)}) } diff --git a/sharedutil/sharedutil.go b/sharedutil/sharedutil.go new file mode 100644 index 0000000..158aaa4 --- /dev/null +++ b/sharedutil/sharedutil.go @@ -0,0 +1,19 @@ +package sharedutil + +import "github.com/dweymouth/go-subsonic/subsonic" + +func FindTrackByID(id string, tracks []*subsonic.Child) *subsonic.Child { + for _, tr := range tracks { + if id == tr.ID { + return tr + } + } + return nil +} + +func TrackIDOrEmptyStr(track *subsonic.Child) string { + if track == nil { + return "" + } + return track.ID +} diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index df0152f..76d44ab 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -5,6 +5,7 @@ import ( "log" "supersonic/backend" "supersonic/res" + "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" "supersonic/ui/util" @@ -100,7 +101,7 @@ func (a *AlbumPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *subso a.nowPlayingID = song.ID } a.tracklist.SetNowPlaying(a.nowPlayingID) - a.tracklist.IncrementPlayCount(lastScrobbledIfAny) + a.tracklist.IncrementPlayCount(sharedutil.TrackIDOrEmptyStr(lastScrobbledIfAny)) } func (a *AlbumPage) Reload() { diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 827b334..a66131c 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -2,6 +2,7 @@ package browsing import ( "supersonic/backend" + "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" "supersonic/ui/widgets" @@ -44,8 +45,9 @@ func NewNowPlayingPage( a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.AutoNumber = true a.tracklist.DisablePlaybackMenu = true + contr.ConnectTracklistActions(a.tracklist) + // override the default OnPlayTrackAt handler b/c we don't need to re-load the tracks into the queue a.tracklist.OnPlayTrackAt = a.onPlayTrackAt - a.tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{ fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue), } @@ -86,7 +88,7 @@ func (a *NowPlayingPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny * a.nowPlayingID = song.ID } a.tracklist.SetNowPlaying(a.nowPlayingID) - a.tracklist.IncrementPlayCount(lastScrobbledIfAny) + a.tracklist.IncrementPlayCount(sharedutil.TrackIDOrEmptyStr(lastScrobbledIfAny)) } func (a *NowPlayingPage) Reload() { diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index f1daef5..bfd3cc3 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -5,6 +5,7 @@ import ( "log" "supersonic/backend" "supersonic/res" + "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" "supersonic/ui/util" @@ -87,7 +88,7 @@ func (a *PlaylistPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *su a.nowPlayingID = song.ID } a.tracklist.SetNowPlaying(a.nowPlayingID) - a.tracklist.IncrementPlayCount(lastScrobbledIfAny) + a.tracklist.IncrementPlayCount(sharedutil.TrackIDOrEmptyStr(lastScrobbledIfAny)) } func (a *PlaylistPage) Reload() { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 270dee0..be192bc 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -62,6 +62,9 @@ func (m Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { } else { go s.Unstar(subsonic.StarParameters{SongIDs: trackIDs}) } + for _, id := range trackIDs { + m.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, fav) + } } } diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index cb05c0a..8041e50 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -5,6 +5,7 @@ import ( "runtime" "strconv" "supersonic/res" + "supersonic/sharedutil" "supersonic/ui/layouts" "supersonic/ui/os" "supersonic/ui/util" @@ -150,16 +151,10 @@ func (t *Tracklist) SetNowPlaying(trackID string) { t.Refresh() } -func (t *Tracklist) IncrementPlayCount(track *subsonic.Child) { - if track == nil { - return - } - for _, tr := range t.Tracks { - if tr.ID == track.ID { - tr.PlayCount += 1 - t.Refresh() - return - } +func (t *Tracklist) IncrementPlayCount(trackID string) { + if tr := sharedutil.FindTrackByID(trackID, t.Tracks); tr != nil { + tr.PlayCount += 1 + t.Refresh() } } @@ -245,6 +240,14 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { } func (t *Tracklist) onSetFavorite(trackID string, fav bool) { + // update our own track model + tr := sharedutil.FindTrackByID(trackID, t.Tracks) + if fav { + tr.Starred = time.Now() + } else { + tr.Starred = time.Time{} + } + // notify listener if t.OnSetFavorite != nil { t.OnSetFavorite([]string{trackID}, fav) } From d86f92c524fb2eec2936ebad5bea305fa810218e Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 22 Feb 2023 19:17:32 -0800 Subject: [PATCH 11/13] bug fix --- backend/playbackmanager.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index f693bec..0e928cd 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -191,11 +191,12 @@ func (p *PlaybackManager) GetPlayQueue() []*subsonic.Child { // Any time the user changes the favorite status of a track elsewhere in the app, // this should be called to ensure the in-memory track model is updated. func (p *PlaybackManager) OnTrackFavoriteStatusChanged(id string, fav bool) { - tr := sharedutil.FindTrackByID(id, p.playQueue) - if fav { - tr.Starred = time.Now() - } else { - tr.Starred = time.Time{} + if tr := sharedutil.FindTrackByID(id, p.playQueue); tr != nil { + if fav { + tr.Starred = time.Now() + } else { + tr.Starred = time.Time{} + } } } From 06bfcfe822965ca10a844dd0802a5a0e205d2f1e Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 22 Feb 2023 19:38:24 -0800 Subject: [PATCH 12/13] persist favorites page view settings --- backend/config.go | 14 ++++++++++++-- ui/browsing/favoritespage.go | 28 +++++++++++++++++++++++----- ui/browsing/router.go | 2 +- ui/widgets/togglebuttongroup.go | 13 +++++++++---- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/backend/config.go b/backend/config.go index da153be..dbe0930 100644 --- a/backend/config.go +++ b/backend/config.go @@ -24,6 +24,11 @@ type AlbumPageConfig struct { TracklistColumns []string } +type FavoritesPageConfig struct { + InitialView string + TracklistColumns []string +} + type NowPlayingPageConfig struct { TracklistColumns []string } @@ -40,6 +45,7 @@ type Config struct { Application AppConfig Servers []*ServerConfig AlbumPage AlbumPageConfig + FavoritesPage FavoritesPageConfig NowPlayingPage NowPlayingPageConfig PlaylistPage PlaylistPageConfig LocalPlayback LocalPlaybackConfig @@ -52,10 +58,14 @@ func DefaultConfig() *Config { WindowHeight: 800, }, AlbumPage: AlbumPageConfig{ - TracklistColumns: []string{"Artist", "Time", "Plays"}, + TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite"}, + }, + FavoritesPage: FavoritesPageConfig{ + TracklistColumns: []string{"Artist", "Album", "Time", "Plays"}, + InitialView: "Albums", }, NowPlayingPage: NowPlayingPageConfig{ - TracklistColumns: []string{"Artist", "Album", "Time"}, + TracklistColumns: []string{"Artist", "Album", "Time", "Plays"}, }, PlaylistPage: PlaylistPageConfig{ TracklistColumns: []string{"Artist", "Album", "Time", "Plays"}, diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 1236612..b40eb74 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -20,6 +20,7 @@ import ( type FavoritesPage struct { widget.BaseWidget + cfg *backend.FavoritesPageConfig contr controller.Controller pm *backend.PlaybackManager im *backend.ImageManager @@ -41,8 +42,9 @@ type FavoritesPage struct { container *fyne.Container } -func NewFavoritesPage(contr controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { +func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { a := &FavoritesPage{ + cfg: cfg, contr: contr, pm: pm, lm: lm, @@ -55,6 +57,13 @@ func NewFavoritesPage(contr controller.Controller, sm *backend.ServerManager, pm a.grid = widgets.NewAlbumGrid(a.lm.StarredIter(), a.im, false) a.connectGridActions() a.createContainer(false) + if cfg.InitialView == "Artists" { + a.toggleBtns.SetActivatedButton(1) + a.onShowFavoriteArtists() + } else if cfg.InitialView == "Songs" { + a.toggleBtns.SetActivatedButton(2) + a.onShowFavoriteSongs() + } return a } @@ -91,6 +100,7 @@ func (a *FavoritesPage) createContainer(searchGrid bool) { func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { a := &FavoritesPage{ + cfg: saved.cfg, contr: saved.contr, pm: saved.pm, lm: saved.lm, @@ -158,7 +168,12 @@ func (a *FavoritesPage) Reload() { } func (a *FavoritesPage) Save() SavedPage { + if a.tracklistCtr != nil { + tl := a.tracklistCtr.Objects[0].(*widgets.Tracklist) + a.cfg.TracklistColumns = tl.VisibleColumns() + } sf := &savedFavoritesPage{ + cfg: a.cfg, contr: a.contr, pm: a.pm, sm: a.sm, @@ -223,6 +238,7 @@ func (a *FavoritesPage) doSearchAlbums(query string) { } func (a *FavoritesPage) onShowFavoriteAlbums() { + a.cfg.InitialView = "Albums" // save setting a.searcher.Entry.Show() if a.searchText == "" { a.container.Objects[0] = a.grid @@ -233,7 +249,8 @@ func (a *FavoritesPage) onShowFavoriteAlbums() { } func (a *FavoritesPage) onShowFavoriteArtists() { - a.searcher.Entry.Hide() // disable search on artists for now + a.cfg.InitialView = "Artists" // save setting + a.searcher.Entry.Hide() // disable search on artists for now if a.artistListCtr == nil { if a.pendingViewSwitch { return @@ -277,7 +294,8 @@ func buildArtistListModel(artists []*subsonic.ArtistID3) []widgets.ArtistGenrePl } func (a *FavoritesPage) onShowFavoriteSongs() { - a.searcher.Entry.Hide() // disable search on songs for now + a.cfg.InitialView = "Songs" // save setting + a.searcher.Entry.Hide() // disable search on songs for now if a.tracklistCtr == nil { if a.pendingViewSwitch { return @@ -291,8 +309,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() { } tracklist := widgets.NewTracklist(s.Song) tracklist.AutoNumber = true - // TODO: get visible columns from config - tracklist.SetVisibleColumns([]string{"Artist", "Album", "Plays"}) + tracklist.SetVisibleColumns(a.cfg.TracklistColumns) tracklist.SetNowPlaying(a.nowPlayingID) a.contr.ConnectTracklistActions(tracklist) a.tracklistCtr = container.New( @@ -326,6 +343,7 @@ func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer { } type savedFavoritesPage struct { + cfg *backend.FavoritesPageConfig contr controller.Controller pm *backend.PlaybackManager sm *backend.ServerManager diff --git a/ui/browsing/router.go b/ui/browsing/router.go index a73079b..beeeb79 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -95,7 +95,7 @@ func (r Router) CreatePage(rte Route) Page { case Artists: return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute) case Favorites: - return NewFavoritesPage(r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genre: return NewGenrePage(rte.Arg, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genres: diff --git a/ui/widgets/togglebuttongroup.go b/ui/widgets/togglebuttongroup.go index bdea9db..bf81d0e 100644 --- a/ui/widgets/togglebuttongroup.go +++ b/ui/widgets/togglebuttongroup.go @@ -43,19 +43,24 @@ func (t *ToggleButtonGroup) ActivatedButtonIndex() int { return t.activeBtnIdx } -func (t *ToggleButtonGroup) onTapped(btnIdx int) bool { +func (t *ToggleButtonGroup) SetActivatedButton(idx int) { + changed := t.activeBtnIdx != idx + t.activeBtnIdx = idx for i, b := range t.buttonContainer.Objects { - if i == btnIdx { + if i == idx { b.(*widget.Button).Importance = widget.HighImportance } else { b.(*widget.Button).Importance = widget.MediumImportance } } - changed := t.activeBtnIdx != btnIdx - t.activeBtnIdx = btnIdx if changed { t.Refresh() } +} + +func (t *ToggleButtonGroup) onTapped(btnIdx int) bool { + changed := t.activeBtnIdx != btnIdx + t.SetActivatedButton(btnIdx) return changed } From dc52f60348fc802da5be8805d52c46e985780360 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 22 Feb 2023 21:15:44 -0800 Subject: [PATCH 13/13] Update README for favorite artists+songs support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa29dbc..024ea4f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Slightly outdated screenshots of Supersonic running against the Navidrome