package widgets import ( "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" ) type GroupedReleasesModel struct { Albums []*GridViewItemModel Compilations []*GridViewItemModel EPs []*GridViewItemModel Singles []*GridViewItemModel } type GroupedReleases struct { widget.BaseWidget ShowSuffix bool Model GroupedReleasesModel OnPlay func(id string, shuffle bool) OnPlayNext func(id string) OnAddToQueue func(id string) OnAddToPlaylist func(id string) OnFavorite func(id string, fav bool) OnDownload func(id string) OnShare func(id string) OnShowItemPage func(id string) OnShowSecondaryPage func(id string) menu *widget.PopUpMenu shareMenuItem *fyne.MenuItem menuGridViewItemId string imageFetcher util.ImageFetcher cardPool sync.Pool sections [4]groupedReleasesSection } type groupedReleasesSection struct { title *widget.Label container *fyne.Container } func NewGroupedReleases(model GroupedReleasesModel, fetch util.ImageFetcher) *GroupedReleases { g := &GroupedReleases{ Model: model, imageFetcher: fetch, } g.cardPool.New = func() any { return g.createNewItemCard() } g.ExtendBaseWidget(g) cardSize := fyne.NewSquareSize(backend.AppInstance().Config.GridView.CardSize) sections := []string{lang.L("Albums"), lang.L("Compilations"), lang.L("EPs"), lang.L("Singles")} for i, s := range sections { g.sections[i].title = widget.NewLabelWithStyle(s, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) g.sections[i].container = container.NewGridWrap(cardSize) } return g } func (g *GroupedReleases) CreateRenderer() fyne.WidgetRenderer { vbox := container.NewVBox() for i := range g.sections { vbox.Add(g.sections[i].title) vbox.Add(g.sections[i].container) } return widget.NewSimpleRenderer(container.NewVScroll(vbox)) } func (g *GroupedReleases) Refresh() { sectionItems := [4][]*GridViewItemModel{g.Model.Albums, g.Model.Compilations, g.Model.EPs, g.Model.Singles} for i, items := range sectionItems { lenItems := len(items) objects := g.sections[i].container.Objects // clear out excess cards in this section for x := lenItems; x < len(objects); x++ { g.cardPool.Put(objects[x]) objects[x] = nil } if lenItems > len(objects) { objects = objects[:lenItems] } // update existing cards for x := 0; x < len(objects); x++ { g.doUpdateItemCard(objects[x].(*GridViewItem), items[x]) } // append new ones as needed for x := len(objects); x < lenItems; x++ { card := g.cardPool.Get().(*GridViewItem) g.doUpdateItemCard(card, items[x]) } g.sections[i].container.Objects = objects // if section has no albums in it, hide if lenItems == 0 { g.sections[i].title.Hide() g.sections[i].container.Hide() } else { g.sections[i].title.Show() g.sections[i].container.Show() } } g.BaseWidget.Refresh() } func (g *GroupedReleases) createNewItemCard() fyne.CanvasObject { card := NewGridViewItem(myTheme.AlbumIcon) card.SetSize(backend.AppInstance().Config.GridView.CardSize) card.ItemIndex = -1 card.ImgLoader = util.NewThumbnailLoader(g.imageFetcher, card.Cover.SetImage) card.ImgLoader.OnBeforeLoad = func() { card.Cover.SetImage(nil) } card.OnPlay = func() { g.onPlay(card.ItemID(), false) } card.OnFavorite = func(fav bool) { if g.OnFavorite != nil { g.OnFavorite(card.itemID, fav) } } card.OnShowSecondaryPage = func(id string) { if g.OnShowSecondaryPage != nil { g.OnShowSecondaryPage(id) } } card.OnShowItemPage = func() { if g.OnShowItemPage != nil { g.OnShowItemPage(card.ItemID()) } } card.OnShowContextMenu = func(p fyne.Position) { g.showContextMenu(card, p) } return card } func (g *GroupedReleases) showContextMenu(card *GridViewItem, pos fyne.Position) { g.menuGridViewItemId = card.ItemID() if g.menu == nil { play := fyne.NewMenuItem(lang.L("Play"), func() { g.onPlay(g.menuGridViewItemId, false) }) play.Icon = theme.MediaPlayIcon() shuffle := fyne.NewMenuItem(lang.L("Shuffle"), func() { g.onPlay(g.menuGridViewItemId, true) }) shuffle.Icon = myTheme.ShuffleIcon queueNext := fyne.NewMenuItem(lang.L("Play next"), func() { if g.OnPlayNext != nil { g.OnPlayNext(g.menuGridViewItemId) } }) queueNext.Icon = myTheme.PlayNextIcon queue := fyne.NewMenuItem(lang.L("Add to queue"), func() { if g.OnAddToQueue != nil { g.OnAddToQueue(g.menuGridViewItemId) } }) queue.Icon = theme.ContentAddIcon() playlist := fyne.NewMenuItem(lang.L("Add to playlist")+"...", func() { if g.OnAddToPlaylist != nil { g.OnAddToPlaylist(g.menuGridViewItemId) } }) playlist.Icon = myTheme.PlaylistIcon download := fyne.NewMenuItem(lang.L("Download")+"...", func() { if g.OnDownload != nil { g.OnDownload(g.menuGridViewItemId) } }) download.Icon = theme.DownloadIcon() g.shareMenuItem = fyne.NewMenuItem(lang.L("Share")+"...", func() { g.OnShare(g.menuGridViewItemId) }) g.shareMenuItem.Icon = myTheme.ShareIcon g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queueNext, queue, playlist, download, g.shareMenuItem), fyne.CurrentApp().Driver().CanvasForObject(g)) } g.menu.ShowAtPosition(pos) } func (g *GroupedReleases) onPlay(itemID string, shuffle bool) { if g.OnPlay != nil { g.OnPlay(itemID, shuffle) } } func (g *GroupedReleases) doUpdateItemCard(card *GridViewItem, model *GridViewItemModel) { card.ShowSuffix = g.ShowSuffix if !card.NeedsUpdate(model) { // nothing to do return } card.Update(model) card.ImgLoader.Load(model.CoverArtID) }