refactor 2: use generic model for grid view

This commit is contained in:
Drew Weymouth
2023-04-28 17:25:57 -07:00
parent cf4540eb32
commit 753e7f9bcc
7 changed files with 95 additions and 65 deletions
+8
View File
@@ -35,6 +35,14 @@ func FilterSlice[T any](ss []T, test func(T) bool) []T {
return result return result
} }
func MapSlice[T any, U any](ts []T, f func(T) U) []U {
result := make([]U, len(ts))
for i, t := range ts {
result[i] = f(t)
}
return result
}
func FindTrackByID(id string, tracks []*subsonic.Child) *subsonic.Child { func FindTrackByID(id string, tracks []*subsonic.Child) *subsonic.Child {
for _, tr := range tracks { for _, tr := range tracks {
if id == tr.ID { if id == tr.ID {
+2 -2
View File
@@ -72,7 +72,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
} }
a.sortOrder.Selected = cfg.SortOrder a.sortOrder.Selected = cfg.SortOrder
iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected))
a.grid = widgets.NewAlbumGrid(iter, im, false /*showYear*/) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im)
a.grid.OnPlay = a.onPlayAlbum a.grid.OnPlay = a.onPlayAlbum
a.grid.OnShowSecondaryPage = a.onShowArtistPage a.grid.OnShowSecondaryPage = a.onShowArtistPage
a.grid.OnShowItemPage = a.onShowAlbumPage a.grid.OnShowItemPage = a.onShowAlbumPage
@@ -180,7 +180,7 @@ func (a *AlbumsPage) Save() SavedPage {
func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) doSearch(query string) {
if a.searchGrid == nil { if a.searchGrid == nil {
a.searchGrid = widgets.NewAlbumGrid(a.lm.SearchIter(query), a.im, false /*showYear*/) a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(a.lm.SearchIter(query)), a.im)
a.searchGrid.OnPlay = a.onPlayAlbum a.searchGrid.OnPlay = a.onPlayAlbum
a.searchGrid.OnShowItemPage = a.onShowAlbumPage a.searchGrid.OnShowItemPage = a.onShowAlbumPage
a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage
+10 -1
View File
@@ -2,6 +2,7 @@ package browsing
import ( import (
"log" "log"
"strconv"
"strings" "strings"
"supersonic/backend" "supersonic/backend"
"supersonic/res" "supersonic/res"
@@ -172,7 +173,15 @@ func (a *ArtistPage) showAlbumGrid() {
a.activeView = 0 // if page still loading, will show discography view first a.activeView = 0 // if page still loading, will show discography view first
return return
} }
a.albumGrid = widgets.NewFixedAlbumGrid(a.artistInfo.Album, a.im, true /*showYear*/) model := sharedutil.MapSlice(a.artistInfo.Album, func(al *subsonic.AlbumID3) widgets.GridViewItemModel {
return widgets.GridViewItemModel{
Name: al.Name,
ID: al.ID,
CoverArtID: al.CoverArt,
Secondary: strconv.Itoa(al.Year),
}
})
a.albumGrid = widgets.NewFixedGridView(model, a.im)
a.albumGrid.OnPlay = a.onPlayAlbum a.albumGrid.OnPlay = a.onPlayAlbum
a.albumGrid.OnShowItemPage = a.onShowAlbumPage a.albumGrid.OnShowItemPage = a.onShowAlbumPage
} }
+2 -2
View File
@@ -53,7 +53,7 @@ func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Contro
} }
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.createHeader(0, "") a.createHeader(0, "")
a.grid = widgets.NewAlbumGrid(a.lm.StarredIter(), a.im, false) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(lm.StarredIter()), a.im)
a.connectGridActions() a.connectGridActions()
if cfg.InitialView == "Artists" { if cfg.InitialView == "Artists" {
a.toggleBtns.SetActivatedButton(1) a.toggleBtns.SetActivatedButton(1)
@@ -220,7 +220,7 @@ func (a *FavoritesPage) doSearchAlbums(query string) {
return al.Starred.After(time.Time{}) return al.Starred.After(time.Time{})
}) })
if a.searchGrid == nil { if a.searchGrid == nil {
a.searchGrid = widgets.NewAlbumGrid(iter, a.im, false /*showYear*/) a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im)
a.searchGrid.OnPlay = a.onPlayAlbum a.searchGrid.OnPlay = a.onPlayAlbum
a.searchGrid.OnShowItemPage = a.onShowAlbumPage a.searchGrid.OnShowItemPage = a.onShowAlbumPage
a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage a.searchGrid.OnShowSecondaryPage = a.onShowArtistPage
+2 -2
View File
@@ -52,7 +52,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
} }
g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs) g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs)
iter := g.lm.GenreIter(g.genre) iter := g.lm.GenreIter(g.genre)
g.grid = widgets.NewAlbumGrid(iter, g.im, false) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.grid.OnPlay = g.onPlayAlbum g.grid.OnPlay = g.onPlayAlbum
g.grid.OnShowSecondaryPage = g.onShowArtistPage g.grid.OnShowSecondaryPage = g.onShowArtistPage
g.grid.OnShowItemPage = g.onShowAlbumPage g.grid.OnShowItemPage = g.onShowAlbumPage
@@ -176,7 +176,7 @@ func (g *GenrePage) doSearch(query string) {
return al.Genre == g.genre return al.Genre == g.genre
}) })
if g.searchGrid == nil { if g.searchGrid == nil {
g.searchGrid = widgets.NewAlbumGrid(iter, g.im, false /*showYear*/) g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.searchGrid.OnPlay = g.onPlayAlbum g.searchGrid.OnPlay = g.onPlayAlbum
g.searchGrid.OnShowItemPage = g.onShowAlbumPage g.searchGrid.OnShowItemPage = g.onShowAlbumPage
g.searchGrid.OnShowSecondaryPage = g.onShowArtistPage g.searchGrid.OnShowSecondaryPage = g.onShowArtistPage
+42 -18
View File
@@ -6,6 +6,7 @@ import (
"log" "log"
"supersonic/backend" "supersonic/backend"
"supersonic/res" "supersonic/res"
"supersonic/sharedutil"
"sync" "sync"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -20,6 +21,31 @@ type ImageFetcher interface {
GetCoverThumbnail(string) (image.Image, error) GetCoverThumbnail(string) (image.Image, error)
} }
type GridViewIterator interface {
NextN(int) []GridViewItemModel
}
type gridViewAlbumIterator struct {
iter *backend.BatchingIterator
}
func (g gridViewAlbumIterator) NextN(n int) []GridViewItemModel {
albums := g.iter.NextN(n)
return sharedutil.MapSlice(albums, func(al *subsonic.AlbumID3) GridViewItemModel {
return GridViewItemModel{
Name: al.Name,
ID: al.ID,
CoverArtID: al.CoverArt,
Secondary: al.Artist,
SecondaryID: al.ArtistID,
}
})
}
func NewGridViewAlbumIterator(iter backend.AlbumIterator) GridViewIterator {
return gridViewAlbumIterator{iter: backend.NewBatchingIterator(iter)}
}
type GridView struct { type GridView struct {
widget.BaseWidget widget.BaseWidget
@@ -29,13 +55,12 @@ type GridView struct {
} }
type GridViewState struct { type GridViewState struct {
items []*subsonic.AlbumID3 items []GridViewItemModel
itemsMutex sync.RWMutex itemsMutex sync.RWMutex
iter *backend.BatchingIterator iter GridViewIterator
highestShown int highestShown int
fetching bool fetching bool
done bool done bool
showYear bool
imageFetcher ImageFetcher imageFetcher ImageFetcher
OnPlay func(string) OnPlay func(string)
@@ -47,13 +72,12 @@ type GridViewState struct {
var _ fyne.Widget = (*GridView)(nil) var _ fyne.Widget = (*GridView)(nil)
func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *GridView { func NewFixedGridView(items []GridViewItemModel, fetch ImageFetcher) *GridView {
g := &GridView{ g := &GridView{
GridViewState: GridViewState{ GridViewState: GridViewState{
items: albums, items: items,
done: true, done: true,
imageFetcher: fetch, imageFetcher: fetch,
showYear: showYear,
}, },
} }
g.ExtendBaseWidget(g) g.ExtendBaseWidget(g)
@@ -61,10 +85,10 @@ func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear
return g return g
} }
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher, showYear bool) *GridView { func NewGridView(iter GridViewIterator, fetch ImageFetcher) *GridView {
g := &GridView{ g := &GridView{
GridViewState: GridViewState{ GridViewState: GridViewState{
iter: backend.NewBatchingIterator(iter), iter: iter,
imageFetcher: fetch, imageFetcher: fetch,
}, },
} }
@@ -106,7 +130,7 @@ func (g *GridView) Reset(iter backend.AlbumIterator) {
g.fetching = false g.fetching = false
g.done = false g.done = false
g.highestShown = 0 g.highestShown = 0
g.iter = backend.NewBatchingIterator(iter) g.iter = gridViewAlbumIterator{iter: backend.NewBatchingIterator(iter)}
g.fetchMoreItems(36) g.fetchMoreItems(36)
} }
@@ -117,7 +141,7 @@ func (g *GridView) createGridWrapList() {
}, },
// create func // create func
func() fyne.CanvasObject { func() fyne.CanvasObject {
card := NewGridViewCard(g.showYear) card := NewGridViewItem()
card.OnPlay = func() { card.OnPlay = func() {
if g.OnPlay != nil { if g.OnPlay != nil {
g.OnPlay(card.ItemID()) g.OnPlay(card.ItemID())
@@ -137,31 +161,31 @@ func (g *GridView) createGridWrapList() {
}, },
// update func // update func
func(itemID int, obj fyne.CanvasObject) { func(itemID int, obj fyne.CanvasObject) {
ac := obj.(*GridViewCard) ac := obj.(*GridViewItem)
g.doUpdateItemCard(itemID, ac) g.doUpdateItemCard(itemID, ac)
}, },
) )
} }
func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewCard) { func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewItem) {
if itemIdx > g.highestShown { if itemIdx > g.highestShown {
g.highestShown = itemIdx g.highestShown = itemIdx
} }
g.itemsMutex.RLock() g.itemsMutex.RLock()
album := g.items[itemIdx] item := g.items[itemIdx]
g.itemsMutex.RUnlock() g.itemsMutex.RUnlock()
if card.PrevID == album.ID { if card.PrevID == item.ID {
// nothing to do // nothing to do
return return
} }
card.Update(album) card.Update(item)
card.PrevID = album.ID card.PrevID = item.ID
// cancel any previous image fetch // cancel any previous image fetch
if card.ImgLoadCancel != nil { if card.ImgLoadCancel != nil {
card.ImgLoadCancel() card.ImgLoadCancel()
card.ImgLoadCancel = nil card.ImgLoadCancel = nil
} }
if img, ok := g.imageFetcher.GetCoverThumbnailFromCache(album.CoverArt); ok { if img, ok := g.imageFetcher.GetCoverThumbnailFromCache(item.CoverArtID); ok {
card.Cover.SetImage(img) card.Cover.SetImage(img)
} else { } else {
card.Cover.SetImageResource(res.ResAlbumplaceholderPng) card.Cover.SetImageResource(res.ResAlbumplaceholderPng)
@@ -169,7 +193,7 @@ func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewCard) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
card.ImgLoadCancel = cancel card.ImgLoadCancel = cancel
go func(ctx context.Context) { go func(ctx context.Context) {
i, err := g.imageFetcher.GetCoverThumbnail(album.CoverArt) i, err := g.imageFetcher.GetCoverThumbnail(item.CoverArtID)
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
@@ -3,7 +3,6 @@ package widgets
import ( import (
"context" "context"
"image" "image"
"strconv"
"supersonic/res" "supersonic/res"
"supersonic/ui/layouts" "supersonic/ui/layouts"
@@ -13,11 +12,9 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
) )
var _ fyne.Widget = (*GridViewCard)(nil) var _ fyne.Widget = (*GridViewItem)(nil)
var _ fyne.Widget = (*coverImage)(nil) var _ fyne.Widget = (*coverImage)(nil)
var _ fyne.Tappable = (*coverImage)(nil) var _ fyne.Tappable = (*coverImage)(nil)
@@ -104,7 +101,7 @@ func isInside(origin fyne.Position, radius float32, point fyne.Position) bool {
return x*x+y*y <= radius*radius return x*x+y*y <= radius*radius
} }
type GridViewCardModel struct { type GridViewItemModel struct {
Name string Name string
ID string ID string
CoverArtID string CoverArtID string
@@ -112,17 +109,14 @@ type GridViewCardModel struct {
SecondaryID string SecondaryID string
} }
type GridViewCard struct { type GridViewItem struct {
widget.BaseWidget widget.BaseWidget
albumID string itemID string
artistID string secondaryID string
title *CustomHyperlink primaryText *CustomHyperlink
artist *CustomHyperlink secondaryText *CustomHyperlink
year *widget.Label container *fyne.Container
container *fyne.Container
showYear bool
// updated by GridView // updated by GridView
Cover *coverImage Cover *coverImage
@@ -136,13 +130,11 @@ type GridViewCard struct {
OnShowSecondaryPage func() OnShowSecondaryPage func()
} }
func NewGridViewCard(showYear bool) *GridViewCard { func NewGridViewItem() *GridViewItem {
g := &GridViewCard{ g := &GridViewItem{
title: NewCustomHyperlink(), primaryText: NewCustomHyperlink(),
artist: NewCustomHyperlink(), secondaryText: NewCustomHyperlink(),
year: widget.NewLabel(""), Cover: newCoverImage(),
Cover: newCoverImage(),
showYear: showYear,
} }
g.ExtendBaseWidget(g) g.ExtendBaseWidget(g)
g.Cover.OnDoubleTapped = func() { g.Cover.OnDoubleTapped = func() {
@@ -156,8 +148,8 @@ func NewGridViewCard(showYear bool) *GridViewCard {
} }
} }
g.Cover.OnTapped = showItemFn g.Cover.OnTapped = showItemFn
g.title.OnTapped = showItemFn g.primaryText.OnTapped = showItemFn
g.artist.OnTapped = func() { g.secondaryText.OnTapped = func() {
if g.OnShowSecondaryPage != nil { if g.OnShowSecondaryPage != nil {
g.OnShowSecondaryPage() g.OnShowSecondaryPage()
} }
@@ -167,34 +159,31 @@ func NewGridViewCard(showYear bool) *GridViewCard {
return g return g
} }
func (g *GridViewCard) createContainer() { func (g *GridViewItem) createContainer() {
var secondLabel fyne.Widget = g.artist info := container.New(&layouts.VboxCustomPadding{ExtraPad: -16}, g.primaryText, g.secondaryText)
if g.showYear {
secondLabel = g.year
}
info := container.New(&layouts.VboxCustomPadding{ExtraPad: -16}, g.title, secondLabel)
c := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, g.Cover, info) c := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, g.Cover, info)
pad := &layouts.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10} pad := &layouts.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
g.container = container.New(pad, c) g.container = container.New(pad, c)
} }
func (g *GridViewCard) Update(al *subsonic.AlbumID3) { func (g *GridViewItem) Update(model GridViewItemModel) {
g.title.SetText(al.Name) g.itemID = model.ID
g.artist.SetText(al.Artist) g.secondaryID = model.SecondaryID
g.year.SetText(strconv.Itoa(al.Year)) g.primaryText.SetText(model.Name)
g.albumID = al.ID g.secondaryText.SetText(model.Secondary)
g.artistID = al.ArtistID g.secondaryText.Disabled = model.SecondaryID == ""
g.secondaryText.Refresh()
g.Cover.playbtn.Hidden = true g.Cover.playbtn.Hidden = true
} }
func (g *GridViewCard) ItemID() string { func (g *GridViewItem) ItemID() string {
return g.albumID return g.itemID
} }
func (g *GridViewCard) SecondaryID() string { func (g *GridViewItem) SecondaryID() string {
return g.artistID return g.secondaryID
} }
func (g *GridViewCard) CreateRenderer() fyne.WidgetRenderer { func (g *GridViewItem) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(g.container) return widget.NewSimpleRenderer(g.container)
} }