From 90936c086dc7f8dff1c21d8e3389d974f47f00b7 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 23 May 2023 18:35:34 -0700 Subject: [PATCH] switch favorite artists view to grid --- ui/browsing/artistsgenrespage.go | 183 ------------------------- ui/browsing/artistspage.go | 10 +- ui/browsing/favoritespage.go | 27 ++-- ui/browsing/genrespage.go | 228 +++++++++++++++++++++++++++++++ ui/browsing/router.go | 2 +- ui/controller/controller.go | 18 ++- ui/widgets/artistgenrelist.go | 115 ---------------- 7 files changed, 260 insertions(+), 323 deletions(-) delete mode 100644 ui/browsing/artistsgenrespage.go create mode 100644 ui/browsing/genrespage.go delete mode 100644 ui/widgets/artistgenrelist.go diff --git a/ui/browsing/artistsgenrespage.go b/ui/browsing/artistsgenrespage.go deleted file mode 100644 index 1b5901f..0000000 --- a/ui/browsing/artistsgenrespage.go +++ /dev/null @@ -1,183 +0,0 @@ -package browsing - -import ( - "log" - "strings" - - "github.com/dweymouth/supersonic/backend/mediaprovider" - "github.com/dweymouth/supersonic/sharedutil" - "github.com/dweymouth/supersonic/ui/controller" - "github.com/dweymouth/supersonic/ui/layouts" - "github.com/dweymouth/supersonic/ui/widgets" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" -) - -var _ fyne.Widget = (*ArtistPage)(nil) - -type ArtistsGenresPage struct { - widget.BaseWidget - - isGenresPage bool - contr *controller.Controller - mp mediaprovider.MediaProvider - model []widgets.ArtistGenreListItemModel - list *widgets.ArtistGenreList - - titleDisp *widget.RichText - container *fyne.Container - searcher *widgets.SearchEntry -} - -func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider) *ArtistsGenresPage { - return newArtistsGenresPage(isGenresPage, contr, mp, "") -} - -func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider, searchText string) *ArtistsGenresPage { - title := "Artists" - if isGenresPage { - title = "Genres" - } - a := &ArtistsGenresPage{ - isGenresPage: isGenresPage, - contr: contr, - mp: mp, - titleDisp: widget.NewRichTextWithText(title), - } - a.ExtendBaseWidget(a) - a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText - a.list = widgets.NewArtistGenreList(nil) - a.list.ShowAlbumCount = true - a.list.ShowTrackCount = isGenresPage - a.list.OnNavTo = func(id string) { - if a.isGenresPage { - a.contr.NavigateTo(controller.GenreRoute(id)) - } else { - a.contr.NavigateTo(controller.ArtistRoute(id)) - } - } - a.searcher = widgets.NewSearchEntry() - a.searcher.OnSearched = a.onSearched - a.searcher.Entry.Text = searchText - a.buildContainer() - go a.load(searchText != "") - return a -} - -// should be called asynchronously -func (a *ArtistsGenresPage) load(searchOnLoad bool) { - if a.isGenresPage { - genres, err := a.mp.GetGenres() - if err != nil { - log.Printf("error loading genres: %v", err.Error()) - } - a.model = a.buildGenresListModel(genres) - } else { - artists, err := a.mp.GetArtists() - if err != nil { - log.Printf("error loading artists: %v", err.Error()) - } - a.model = a.buildArtistListModel(artists) - } - if searchOnLoad { - a.onSearched(a.searcher.Entry.Text) - } else { - a.list.Items = a.model - a.list.Refresh() - } -} - -func (a *ArtistsGenresPage) onSearched(query string) { - // since the artists and genres lists are returned in full non-paginated, we will do our own - // simple search based on the artist/genre name, rather than calling a server API - if query == "" { - a.list.Items = a.model - } else { - result := sharedutil.FilterSlice(a.model, func(x widgets.ArtistGenreListItemModel) bool { - return strings.Contains(strings.ToLower(x.Name), strings.ToLower(query)) - }) - a.list.Items = result - } - a.list.Refresh() -} - -var _ Searchable = (*ArtistsGenresPage)(nil) - -func (a *ArtistsGenresPage) SearchWidget() fyne.Focusable { - return a.searcher -} - -func (a *ArtistsGenresPage) Route() controller.Route { - if a.isGenresPage { - return controller.GenresRoute() - } - return controller.ArtistsRoute() -} - -func (a *ArtistsGenresPage) Reload() { - go a.load(false) -} - -func (a *ArtistsGenresPage) Save() SavedPage { - return &savedArtistsGenresPage{ - isGenresPage: a.isGenresPage, - contr: a.contr, - mp: a.mp, - searchText: a.searcher.Entry.Text, - } -} - -type savedArtistsGenresPage struct { - isGenresPage bool - contr *controller.Controller - mp mediaprovider.MediaProvider - searchText string -} - -func (s *savedArtistsGenresPage) Restore() Page { - return newArtistsGenresPage(s.isGenresPage, s.contr, s.mp, s.searchText) -} - -func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel { - model := make([]widgets.ArtistGenreListItemModel, 0) - for _, artist := range artists { - model = append(model, widgets.ArtistGenreListItemModel{ - ID: artist.ID, - Name: artist.Name, - AlbumCount: artist.AlbumCount, - Favorite: artist.Favorite, - }) - } - return model -} - -func (a *ArtistsGenresPage) buildGenresListModel(genres []*mediaprovider.Genre) []widgets.ArtistGenreListItemModel { - model := make([]widgets.ArtistGenreListItemModel, 0) - for _, genre := range genres { - model = append(model, widgets.ArtistGenreListItemModel{ - ID: genre.Name, - Name: genre.Name, - AlbumCount: genre.AlbumCount, - TrackCount: genre.TrackCount, - Favorite: false, - }) - } - return model -} - -func (a *ArtistsGenresPage) buildContainer() { - searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer()) - a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, - container.NewBorder( - container.New(&layouts.MaxPadLayout{PadLeft: -5}, - container.NewHBox(a.titleDisp, layout.NewSpacer(), searchVbox)), - nil, nil, nil, a.list)) -} - -func (a *ArtistsGenresPage) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(a.container) -} diff --git a/ui/browsing/artistspage.go b/ui/browsing/artistspage.go index d4153b6..5bfcc94 100644 --- a/ui/browsing/artistspage.go +++ b/ui/browsing/artistspage.go @@ -76,15 +76,7 @@ func newArtistsPage( a.searcher.OnSearched = func(query string) { a.onSearched(query, false /*firstLoad*/) } a.searcher.Entry.Text = searchText a.grid = widgets.NewFixedGridView(nil, a.im, myTheme.ArtistIcon) - a.grid.OnShowItemPage = a.showArtistPage - a.grid.OnPlay = func(artistID string, shuffle bool) { go a.contr.PlayArtistDiscography(artistID, shuffle) } - a.grid.OnAddToQueue = func(artistID string) { - go a.pm.LoadTracks(a.contr.GetArtistTracks(artistID), true /*append*/, false /*shuffle*/) - } - a.grid.OnAddToPlaylist = func(artistID string) { - go a.contr.DoAddTracksToPlaylistWorkflow( - sharedutil.TracksToIDs(a.contr.GetArtistTracks(artistID))) - } + a.contr.ConnectArtistGridActions(a.grid) searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer()) a.container = container.NewBorder( diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 3e69365..70e37f9 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -1,6 +1,7 @@ package browsing import ( + "fmt" "log" "github.com/dweymouth/supersonic/backend" @@ -162,8 +163,8 @@ func (a *FavoritesPage) Reload() { } if a.artistListCtr != nil { // refresh favorite artists view - al := a.artistListCtr.Objects[0].(*widgets.ArtistGenreList) - al.Items = buildArtistListModel(starred.Artists) + al := a.artistListCtr.Objects[0].(*widgets.GridView) + al.ResetFixed(buildArtistListModel(starred.Artists)) if a.toggleBtns.ActivatedButtonIndex() == 1 { // favorite artists view is visible al.Refresh() @@ -273,14 +274,11 @@ func (a *FavoritesPage) onShowFavoriteArtists() { return } model := buildArtistListModel(fav.Artists) - artistList := widgets.NewArtistGenreList(model) - artistList.ShowAlbumCount = true - artistList.OnNavTo = func(artistID string) { - a.contr.NavigateTo(controller.ArtistRoute(artistID)) - } + artistGrid := widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon) + a.contr.ConnectArtistGridActions(artistGrid) a.artistListCtr = container.New( &layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, - artistList) + artistGrid) a.container.Objects[0] = a.artistListCtr a.Refresh() a.pendingViewSwitch = false @@ -291,13 +289,18 @@ func (a *FavoritesPage) onShowFavoriteArtists() { } } -func buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel { - model := make([]widgets.ArtistGenreListItemModel, 0) +func buildArtistListModel(artists []*mediaprovider.Artist) []widgets.GridViewItemModel { + model := make([]widgets.GridViewItemModel, 0) for _, ar := range artists { - model = append(model, widgets.ArtistGenreListItemModel{ + albums := "albums" + if ar.AlbumCount == 1 { + albums = "album" + } + model = append(model, widgets.GridViewItemModel{ ID: ar.ID, + CoverArtID: ar.CoverArtID, Name: ar.Name, - AlbumCount: ar.AlbumCount, + Secondary: fmt.Sprintf("%d %s", ar.AlbumCount, albums), }) } return model diff --git a/ui/browsing/genrespage.go b/ui/browsing/genrespage.go new file mode 100644 index 0000000..f8ce5f5 --- /dev/null +++ b/ui/browsing/genrespage.go @@ -0,0 +1,228 @@ +package browsing + +import ( + "log" + "strconv" + "strings" + + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/sharedutil" + "github.com/dweymouth/supersonic/ui/controller" + "github.com/dweymouth/supersonic/ui/layouts" + "github.com/dweymouth/supersonic/ui/widgets" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +var _ fyne.Widget = (*ArtistPage)(nil) + +type GenresPage struct { + widget.BaseWidget + + contr *controller.Controller + mp mediaprovider.MediaProvider + genres []*mediaprovider.Genre + list *GenreList + + titleDisp *widget.RichText + container *fyne.Container + searcher *widgets.SearchEntry +} + +func NewGenresPage(contr *controller.Controller, mp mediaprovider.MediaProvider) *GenresPage { + return newGenresPage(contr, mp, "") +} + +func newGenresPage(contr *controller.Controller, mp mediaprovider.MediaProvider, searchText string) *GenresPage { + a := &GenresPage{ + contr: contr, + mp: mp, + titleDisp: widget.NewRichTextWithText("Genres"), + } + a.ExtendBaseWidget(a) + a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText + a.list = NewGenreList(nil) + a.list.ShowAlbumCount = true + a.list.ShowTrackCount = true + a.list.OnNavTo = func(id string) { a.contr.NavigateTo(controller.GenreRoute(id)) } + a.searcher = widgets.NewSearchEntry() + a.searcher.OnSearched = a.onSearched + a.searcher.Entry.Text = searchText + a.buildContainer() + go a.load(searchText != "") + return a +} + +// should be called asynchronously +func (a *GenresPage) load(searchOnLoad bool) { + genres, err := a.mp.GetGenres() + if err != nil { + log.Printf("error loading genres: %v", err.Error()) + } + if searchOnLoad { + a.onSearched(a.searcher.Entry.Text) + } else { + a.list.Items = genres + a.list.Refresh() + } +} + +func (a *GenresPage) onSearched(query string) { + // since the artists and genres lists are returned in full non-paginated, we will do our own + // simple search based on the artist/genre name, rather than calling a server API + if query == "" { + a.list.Items = a.genres + } else { + result := sharedutil.FilterSlice(a.genres, func(x *mediaprovider.Genre) bool { + return strings.Contains(strings.ToLower(x.Name), strings.ToLower(query)) + }) + a.list.Items = result + } + a.list.Refresh() +} + +var _ Searchable = (*GenresPage)(nil) + +func (a *GenresPage) SearchWidget() fyne.Focusable { + return a.searcher +} + +func (a *GenresPage) Route() controller.Route { + return controller.GenresRoute() +} + +func (a *GenresPage) Reload() { + go a.load(false) +} + +func (a *GenresPage) Save() SavedPage { + return &savedArtistsGenresPage{ + contr: a.contr, + mp: a.mp, + searchText: a.searcher.Entry.Text, + } +} + +type savedArtistsGenresPage struct { + isGenresPage bool + contr *controller.Controller + mp mediaprovider.MediaProvider + searchText string +} + +func (s *savedArtistsGenresPage) Restore() Page { + return newGenresPage(s.contr, s.mp, s.searchText) +} + +func (a *GenresPage) buildContainer() { + searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer()) + a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, + container.NewBorder( + container.New(&layouts.MaxPadLayout{PadLeft: -5}, + container.NewHBox(a.titleDisp, layout.NewSpacer(), searchVbox)), + nil, nil, nil, a.list)) +} + +func (a *GenresPage) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} + +type GenreList struct { + widget.BaseWidget + + Items []*mediaprovider.Genre + ShowAlbumCount bool + ShowTrackCount bool + OnNavTo func(string) + + columnsLayout *layouts.ColumnsLayout + hdr *widgets.ListHeader + list *widget.List + container *fyne.Container +} + +type GenreListRow struct { + widget.BaseWidget + + Item *mediaprovider.Genre + OnTapped func() + + nameLabel *widget.Label + albumCountLabel *widget.Label + trackCountLabel *widget.Label + + container *fyne.Container +} + +func NewGenreListRow(layout *layouts.ColumnsLayout) *GenreListRow { + a := &GenreListRow{ + nameLabel: widget.NewLabel(""), + albumCountLabel: widget.NewLabel(""), + trackCountLabel: widget.NewLabel(""), + } + a.ExtendBaseWidget(a) + a.albumCountLabel.Alignment = fyne.TextAlignTrailing + a.trackCountLabel.Alignment = fyne.TextAlignTrailing + a.container = container.New(layout, a.nameLabel, a.albumCountLabel, a.trackCountLabel) + return a +} + +func NewGenreList(items []*mediaprovider.Genre) *GenreList { + a := &GenreList{ + Items: items, + columnsLayout: layouts.NewColumnsLayout([]float32{-1, 125, 125}), + } + a.ExtendBaseWidget(a) + a.hdr = widgets.NewListHeader([]widgets.ListColumn{ + {"Name", false, false}, {"Album Count", true, false}, {"Track Count", true, false}}, a.columnsLayout) + a.list = widget.NewList( + func() int { return len(a.Items) }, + func() fyne.CanvasObject { + r := NewGenreListRow(a.columnsLayout) + r.OnTapped = func() { a.onRowDoubleTapped(r.Item) } + return r + }, + func(id widget.ListItemID, item fyne.CanvasObject) { + row := item.(*GenreListRow) + row.Item = a.Items[id] + row.albumCountLabel.Hidden = !a.ShowAlbumCount + row.trackCountLabel.Hidden = !a.ShowTrackCount + row.nameLabel.Text = row.Item.Name + row.albumCountLabel.Text = strconv.Itoa(row.Item.AlbumCount) + row.trackCountLabel.Text = strconv.Itoa(row.Item.TrackCount) + row.Refresh() + }, + ) + a.container = container.NewBorder(a.hdr, nil, nil, nil, a.list) + return a +} + +func (a *GenreList) Refresh() { + a.hdr.SetColumnVisible(1, a.ShowAlbumCount) + a.hdr.SetColumnVisible(2, a.ShowTrackCount) + a.BaseWidget.Refresh() +} + +func (a *GenreList) onRowDoubleTapped(item *mediaprovider.Genre) { + if a.OnNavTo != nil { + a.OnNavTo(item.Name) + } +} + +func (a *GenreListRow) Tapped(*fyne.PointEvent) { + if a.OnTapped != nil { + a.OnTapped() + } +} + +func (a *GenreList) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} + +func (a *GenreListRow) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} diff --git a/ui/browsing/router.go b/ui/browsing/router.go index 2ab39e7..e0f710b 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -40,7 +40,7 @@ func (r Router) CreatePage(rte controller.Route) Page { case controller.Genre: return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Genres: - return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager.Server) + return NewGenresPage(r.Controller, r.App.ServerManager.Server) case controller.NowPlaying: return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager) case controller.Playlist: diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 9b098f1..6721819 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -57,7 +57,7 @@ func (m *Controller) CloseEscapablePopUp() { } // If there is currently no modal popup managed by the Controller visible, -// then run f (which should create and show a modal dialog) immediately. +// then run f (which should create and show modal dialog) immediately. // else run f when the current modal dialog workflow has ended. func (m *Controller) QueueShowModalFunc(f func()) { if m.haveModal { @@ -140,6 +140,18 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { } } +func (m *Controller) ConnectArtistGridActions(grid *widgets.GridView) { + grid.OnShowItemPage = func(id string) { m.NavigateTo(ArtistRoute(id)) } + grid.OnPlay = func(artistID string, shuffle bool) { go m.PlayArtistDiscography(artistID, shuffle) } + grid.OnAddToQueue = func(artistID string) { + go m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), true /*append*/, false /*shuffle*/) + } + grid.OnAddToPlaylist = func(artistID string) { + go m.DoAddTracksToPlaylistWorkflow( + sharedutil.TracksToIDs(m.GetArtistTracks(artistID))) + } +} + func (m *Controller) GetArtistTracks(artistID string) []*mediaprovider.Track { server := m.App.ServerManager.Server if server == nil { @@ -198,7 +210,7 @@ func (m *Controller) PromptForFirstServer() { } // Show dialog to prompt for playlist. -// Depending on the results of that dialog, potentially create a new playlist +// Depending on the results of that dialog, potentially create new playlist // Add tracks to the user-specified playlist func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { go func() { @@ -297,7 +309,7 @@ func (c *Controller) DoConnectToServerWorkflow(server *backend.ServerConfig) { } func (m *Controller) PromptForLoginAndConnect() { - // TODO: this will need to be rewritten a bit when we support multi servers + // TODO: this will need to be rewritten bit when we support multi servers // need to make sure the intended server is first in the list passed to NewLoginDialog d := dialogs.NewLoginDialog(m.App.Config.Servers, m.App.ServerManager.GetServerPassword) pop := widget.NewModalPopUp(d, m.MainWindow.Canvas()) diff --git a/ui/widgets/artistgenrelist.go b/ui/widgets/artistgenrelist.go deleted file mode 100644 index 08a60bf..0000000 --- a/ui/widgets/artistgenrelist.go +++ /dev/null @@ -1,115 +0,0 @@ -package widgets - -import ( - "strconv" - - "github.com/dweymouth/supersonic/ui/layouts" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" -) - -type ArtistGenreListItemModel struct { - ID string - Name string - AlbumCount int - TrackCount int - Favorite bool -} - -type ArtistGenreList struct { - widget.BaseWidget - - Items []ArtistGenreListItemModel - ShowAlbumCount bool - ShowTrackCount bool - OnNavTo func(string) - - columnsLayout *layouts.ColumnsLayout - hdr *ListHeader - list *widget.List - container *fyne.Container -} - -type ArtistGenreListRow struct { - widget.BaseWidget - - Item ArtistGenreListItemModel - OnTapped func() - - nameLabel *widget.Label - albumCountLabel *widget.Label - trackCountLabel *widget.Label - - container *fyne.Container -} - -func NewArtistGenreListRow(layout *layouts.ColumnsLayout) *ArtistGenreListRow { - a := &ArtistGenreListRow{ - nameLabel: widget.NewLabel(""), - albumCountLabel: widget.NewLabel(""), - trackCountLabel: widget.NewLabel(""), - } - a.ExtendBaseWidget(a) - a.albumCountLabel.Alignment = fyne.TextAlignTrailing - a.trackCountLabel.Alignment = fyne.TextAlignTrailing - a.container = container.New(layout, a.nameLabel, a.albumCountLabel, a.trackCountLabel) - return a -} - -func NewArtistGenreList(items []ArtistGenreListItemModel) *ArtistGenreList { - a := &ArtistGenreList{ - Items: items, - columnsLayout: layouts.NewColumnsLayout([]float32{-1, 125, 125}), - } - a.ExtendBaseWidget(a) - a.hdr = NewListHeader([]ListColumn{ - {"Name", false, false}, {"Album Count", true, false}, {"Track Count", true, false}}, a.columnsLayout) - a.list = widget.NewList( - func() int { return len(a.Items) }, - func() fyne.CanvasObject { - r := NewArtistGenreListRow(a.columnsLayout) - r.OnTapped = func() { a.onRowDoubleTapped(r.Item) } - return r - }, - func(id widget.ListItemID, item fyne.CanvasObject) { - row := item.(*ArtistGenreListRow) - row.Item = a.Items[id] - row.albumCountLabel.Hidden = !a.ShowAlbumCount - row.trackCountLabel.Hidden = !a.ShowTrackCount - row.nameLabel.Text = row.Item.Name - row.albumCountLabel.Text = strconv.Itoa(row.Item.AlbumCount) - row.trackCountLabel.Text = strconv.Itoa(row.Item.TrackCount) - row.Refresh() - }, - ) - a.container = container.NewBorder(a.hdr, nil, nil, nil, a.list) - return a -} - -func (a *ArtistGenreList) Refresh() { - a.hdr.SetColumnVisible(1, a.ShowAlbumCount) - a.hdr.SetColumnVisible(2, a.ShowTrackCount) - a.BaseWidget.Refresh() -} - -func (a *ArtistGenreList) onRowDoubleTapped(item ArtistGenreListItemModel) { - if a.OnNavTo != nil { - a.OnNavTo(item.ID) - } -} - -func (a *ArtistGenreListRow) Tapped(*fyne.PointEvent) { - if a.OnTapped != nil { - a.OnTapped() - } -} - -func (a *ArtistGenreList) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(a.container) -} - -func (a *ArtistGenreListRow) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(a.container) -}