273 lines
7.5 KiB
Go
273 lines
7.5 KiB
Go
package widgets
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/lang"
|
|
"fyne.io/fyne/v2/layout"
|
|
"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 {
|
|
titleRow *groupedReleasesHeader
|
|
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 {
|
|
_i := i
|
|
g.sections[i].titleRow = newGroupedReleasesHeader(s, func(collapse bool) {
|
|
if collapse {
|
|
g.sections[_i].container.Hide()
|
|
} else {
|
|
g.sections[_i].container.Show()
|
|
}
|
|
canvas.Refresh(g)
|
|
})
|
|
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].titleRow)
|
|
vbox.Add(g.sections[i].container)
|
|
}
|
|
return widget.NewSimpleRenderer(container.NewVScroll(vbox))
|
|
}
|
|
|
|
func (g *GroupedReleases) Refresh() {
|
|
gridSize := backend.AppInstance().Config.GridView.CardSize
|
|
sectionItems := [4][]GridViewItemModel{g.Model.Albums, g.Model.Compilations, g.Model.EPs, g.Model.Singles}
|
|
layoutSize := fyne.NewSize(0, 0)
|
|
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 len(objects) > lenItems {
|
|
objects = objects[:lenItems]
|
|
}
|
|
|
|
// update existing cards
|
|
for x := 0; x < len(objects); x++ {
|
|
g.doUpdateItemCard(objects[x].(*GridViewItem), &items[x])
|
|
objects[x].(*GridViewItem).SetSize(gridSize)
|
|
if layoutSize.IsZero() {
|
|
layoutSize = objects[x].MinSize()
|
|
}
|
|
}
|
|
// append new ones as needed
|
|
for x := len(objects); x < lenItems; x++ {
|
|
card := g.cardPool.Get().(*GridViewItem)
|
|
card.SetSize(gridSize)
|
|
g.doUpdateItemCard(card, &items[x])
|
|
objects = append(objects, card)
|
|
if layoutSize.IsZero() {
|
|
layoutSize = card.MinSize()
|
|
}
|
|
}
|
|
|
|
l := layout.NewGridWrapLayout(layoutSize)
|
|
g.sections[i].container.Layout = l
|
|
// needed to initialize the col count so that MinSize is correct
|
|
l.Layout(objects, g.Size())
|
|
g.sections[i].container.Objects = objects
|
|
// if section has no albums in it, hide
|
|
if lenItems == 0 {
|
|
g.sections[i].titleRow.Hide()
|
|
g.sections[i].container.Hide()
|
|
} else {
|
|
g.sections[i].titleRow.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)
|
|
}
|
|
|
|
type groupedReleasesHeader struct {
|
|
widget.BaseWidget
|
|
|
|
title string
|
|
onToggle func(bool)
|
|
|
|
icon *widget.Icon
|
|
collapsed bool
|
|
}
|
|
|
|
func newGroupedReleasesHeader(title string, onToggleVisibility func(bool)) *groupedReleasesHeader {
|
|
g := &groupedReleasesHeader{
|
|
title: title,
|
|
icon: widget.NewIcon(theme.MenuDropDownIcon()),
|
|
onToggle: onToggleVisibility,
|
|
}
|
|
g.ExtendBaseWidget(g)
|
|
return g
|
|
}
|
|
|
|
func (g *groupedReleasesHeader) CreateRenderer() fyne.WidgetRenderer {
|
|
titleText := widget.NewLabelWithStyle(g.title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
|
titleText.SizeName = theme.SizeNameSubHeadingText
|
|
return widget.NewSimpleRenderer(
|
|
container.NewHBox(util.NewHSpace(8), titleText, util.NewHSpace(-theme.Padding()*2), g.icon),
|
|
)
|
|
}
|
|
|
|
func (g *groupedReleasesHeader) Refresh() {
|
|
if g.collapsed {
|
|
g.icon.Resource = theme.MenuExpandIcon()
|
|
} else {
|
|
g.icon.Resource = theme.MenuDropDownIcon()
|
|
}
|
|
g.BaseWidget.Refresh()
|
|
}
|
|
|
|
var _ fyne.Tappable = (*groupedReleasesHeader)(nil)
|
|
|
|
func (g *groupedReleasesHeader) Tapped(*fyne.PointEvent) {
|
|
g.collapsed = !g.collapsed
|
|
g.onToggle(g.collapsed)
|
|
g.Refresh()
|
|
}
|