Merge pull request #16 from dweymouth/develop
Add artist page and navigation
This commit is contained in:
@@ -37,6 +37,10 @@ func StartupApp() (*App, error) {
|
|||||||
a := &App{}
|
a := &App{}
|
||||||
a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
|
a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
log.Printf("Starting %s...", AppName)
|
||||||
|
log.Printf("Using config dir: %s", configdir.LocalConfig(AppName))
|
||||||
|
log.Printf("Using cache dir: %s", configdir.LocalCache(AppName))
|
||||||
|
|
||||||
a.readConfig()
|
a.readConfig()
|
||||||
|
|
||||||
if err := a.initMPV(); err != nil {
|
if err := a.initMPV(); err != nil {
|
||||||
|
|||||||
+48
-22
@@ -3,6 +3,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"image"
|
"image"
|
||||||
|
"log"
|
||||||
"supersonic/backend"
|
"supersonic/backend"
|
||||||
"supersonic/ui/widgets"
|
"supersonic/ui/widgets"
|
||||||
|
|
||||||
@@ -21,41 +22,35 @@ type AlbumGrid struct {
|
|||||||
iter backend.AlbumIterator
|
iter backend.AlbumIterator
|
||||||
fetching bool
|
fetching bool
|
||||||
done bool
|
done bool
|
||||||
|
showYear bool
|
||||||
|
|
||||||
imageFetcher ImageFetcher
|
imageFetcher ImageFetcher
|
||||||
OnPlayAlbum func(string)
|
OnPlayAlbum func(string)
|
||||||
|
OnShowArtistPage func(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ fyne.Widget = (*AlbumGrid)(nil)
|
var _ fyne.Widget = (*AlbumGrid)(nil)
|
||||||
|
|
||||||
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher) *AlbumGrid {
|
func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *AlbumGrid {
|
||||||
|
ag := &AlbumGrid{
|
||||||
|
albums: albums,
|
||||||
|
done: true,
|
||||||
|
imageFetcher: fetch,
|
||||||
|
showYear: showYear,
|
||||||
|
}
|
||||||
|
ag.ExtendBaseWidget(ag)
|
||||||
|
ag.createGridWrapList()
|
||||||
|
return ag
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlbumGrid(iter backend.AlbumIterator, fetch ImageFetcher, showYear bool) *AlbumGrid {
|
||||||
ag := &AlbumGrid{
|
ag := &AlbumGrid{
|
||||||
iter: iter,
|
iter: iter,
|
||||||
imageFetcher: fetch,
|
imageFetcher: fetch,
|
||||||
}
|
}
|
||||||
ag.ExtendBaseWidget(ag)
|
ag.ExtendBaseWidget(ag)
|
||||||
|
|
||||||
g := widget.NewGridWrapList(
|
ag.createGridWrapList()
|
||||||
func() int {
|
|
||||||
return len(ag.albums)
|
|
||||||
},
|
|
||||||
// create func
|
|
||||||
func() fyne.CanvasObject {
|
|
||||||
ac := widgets.NewAlbumCard()
|
|
||||||
ac.OnPlay = func() {
|
|
||||||
if ag.OnPlayAlbum != nil {
|
|
||||||
ag.OnPlayAlbum(ac.AlbumID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ac
|
|
||||||
},
|
|
||||||
// update func
|
|
||||||
func(itemID int, obj fyne.CanvasObject) {
|
|
||||||
ac := obj.(*widgets.AlbumCard)
|
|
||||||
ag.doUpdateAlbumCard(itemID, ac)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ag.grid = g
|
|
||||||
|
|
||||||
// fetch initial albums
|
// fetch initial albums
|
||||||
ag.fetchMoreAlbums(36)
|
ag.fetchMoreAlbums(36)
|
||||||
@@ -75,6 +70,35 @@ func (ag *AlbumGrid) Reset(iter backend.AlbumIterator) {
|
|||||||
ag.fetchMoreAlbums(36)
|
ag.fetchMoreAlbums(36)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ag *AlbumGrid) createGridWrapList() {
|
||||||
|
g := widget.NewGridWrapList(
|
||||||
|
func() int {
|
||||||
|
return len(ag.albums)
|
||||||
|
},
|
||||||
|
// create func
|
||||||
|
func() fyne.CanvasObject {
|
||||||
|
ac := widgets.NewAlbumCard(ag.showYear)
|
||||||
|
ac.OnPlay = func() {
|
||||||
|
if ag.OnPlayAlbum != nil {
|
||||||
|
ag.OnPlayAlbum(ac.AlbumID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ac.OnShowArtistPage = func() {
|
||||||
|
if ag.OnShowArtistPage != nil {
|
||||||
|
ag.OnShowArtistPage(ac.ArtistID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ac
|
||||||
|
},
|
||||||
|
// update func
|
||||||
|
func(itemID int, obj fyne.CanvasObject) {
|
||||||
|
ac := obj.(*widgets.AlbumCard)
|
||||||
|
ag.doUpdateAlbumCard(itemID, ac)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
ag.grid = g
|
||||||
|
}
|
||||||
|
|
||||||
func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *widgets.AlbumCard) {
|
func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *widgets.AlbumCard) {
|
||||||
album := ag.albums[albumIdx]
|
album := ag.albums[albumIdx]
|
||||||
if ac.PrevAlbumID == album.ID {
|
if ac.PrevAlbumID == album.ID {
|
||||||
@@ -99,6 +123,8 @@ func (ag *AlbumGrid) doUpdateAlbumCard(albumIdx int, ac *widgets.AlbumCard) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ac.Cover.SetImage(i)
|
ac.Cover.SetImage(i)
|
||||||
ac.Cover.Refresh()
|
ac.Cover.Refresh()
|
||||||
|
} else {
|
||||||
|
log.Printf("error fetching image: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(ctx)
|
}(ctx)
|
||||||
|
|||||||
+17
-4
@@ -18,6 +18,7 @@ type AlbumsPage struct {
|
|||||||
|
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
lm *backend.LibraryManager
|
lm *backend.LibraryManager
|
||||||
|
nav func(Route)
|
||||||
grid *AlbumGrid
|
grid *AlbumGrid
|
||||||
searchGrid *AlbumGrid
|
searchGrid *AlbumGrid
|
||||||
searchText string
|
searchText string
|
||||||
@@ -48,10 +49,11 @@ func (s *selectWidget) MinSize() fyne.Size {
|
|||||||
return fyne.NewSize(170, s.height)
|
return fyne.NewSize(170, s.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlbumsPage(title string, lm *backend.LibraryManager, im *backend.ImageManager) *AlbumsPage {
|
func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumsPage {
|
||||||
a := &AlbumsPage{
|
a := &AlbumsPage{
|
||||||
lm: lm,
|
lm: lm,
|
||||||
im: im,
|
im: im,
|
||||||
|
nav: nav,
|
||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
|
|
||||||
@@ -60,11 +62,13 @@ func NewAlbumsPage(title string, lm *backend.LibraryManager, im *backend.ImageMa
|
|||||||
SizeName: theme.SizeNameHeadingText,
|
SizeName: theme.SizeNameHeadingText,
|
||||||
}
|
}
|
||||||
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
|
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
|
||||||
a.sortOrder.Selected = a.sortOrder.Options[0]
|
a.sortOrder.Selected = sortOrder
|
||||||
a.sortOrder.OnChanged = a.onSortOrderChanged
|
a.sortOrder.OnChanged = a.onSortOrderChanged
|
||||||
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
|
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
|
||||||
a.grid = NewAlbumGrid(lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)), im.GetAlbumThumbnail)
|
iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected))
|
||||||
|
a.grid = NewAlbumGrid(iter, im.GetAlbumThumbnail, false /*showYear*/)
|
||||||
a.grid.OnPlayAlbum = a.onPlayAlbum
|
a.grid.OnPlayAlbum = a.onPlayAlbum
|
||||||
|
a.grid.OnShowArtistPage = a.onShowArtistPage
|
||||||
a.container = container.NewBorder(
|
a.container = container.NewBorder(
|
||||||
container.NewHBox(widgets.NewHSpace(15), a.titleDisp, sortVbox),
|
container.NewHBox(widgets.NewHSpace(15), a.titleDisp, sortVbox),
|
||||||
nil,
|
nil,
|
||||||
@@ -86,8 +90,9 @@ func (a *AlbumsPage) OnSearched(query string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if a.searchGrid == nil {
|
if a.searchGrid == nil {
|
||||||
a.searchGrid = NewAlbumGrid(a.lm.SearchIter(query), a.im.GetAlbumThumbnail)
|
a.searchGrid = NewAlbumGrid(a.lm.SearchIter(query), a.im.GetAlbumThumbnail, false /*showYear*/)
|
||||||
a.searchGrid.OnPlayAlbum = a.onPlayAlbum
|
a.searchGrid.OnPlayAlbum = a.onPlayAlbum
|
||||||
|
a.searchGrid.OnShowArtistPage = a.onShowArtistPage
|
||||||
} else {
|
} else {
|
||||||
a.searchGrid.Reset(a.lm.SearchIter(query))
|
a.searchGrid.Reset(a.lm.SearchIter(query))
|
||||||
}
|
}
|
||||||
@@ -95,12 +100,20 @@ func (a *AlbumsPage) OnSearched(query string) {
|
|||||||
a.Refresh()
|
a.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AlbumsPage) SetPlayAlbumCallback(cb func(string)) {
|
||||||
|
a.OnPlayAlbum = cb
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) onPlayAlbum(albumID string) {
|
func (a *AlbumsPage) onPlayAlbum(albumID string) {
|
||||||
if a.OnPlayAlbum != nil {
|
if a.OnPlayAlbum != nil {
|
||||||
a.OnPlayAlbum(albumID)
|
a.OnPlayAlbum(albumID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AlbumsPage) onShowArtistPage(artistID string) {
|
||||||
|
a.nav(ArtistRoute(artistID))
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) onSortOrderChanged(order string) {
|
func (a *AlbumsPage) onSortOrderChanged(order string) {
|
||||||
a.grid.Reset(a.lm.AlbumsIter(backend.AlbumSortOrder(order)))
|
a.grid.Reset(a.lm.AlbumsIter(backend.AlbumSortOrder(order)))
|
||||||
if a.searchText == "" {
|
if a.searchText == "" {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"supersonic/backend"
|
||||||
|
|
||||||
|
"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 ArtistPage struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
artistID string
|
||||||
|
im *backend.ImageManager
|
||||||
|
sm *backend.ServerManager
|
||||||
|
grid *AlbumGrid
|
||||||
|
titleDisp *widget.RichText
|
||||||
|
container *fyne.Container
|
||||||
|
OnPlayAlbum func(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArtistPage(artistID string, sm *backend.ServerManager, im *backend.ImageManager) *ArtistPage {
|
||||||
|
a := &ArtistPage{
|
||||||
|
artistID: artistID,
|
||||||
|
sm: sm,
|
||||||
|
im: im,
|
||||||
|
}
|
||||||
|
a.ExtendBaseWidget(a)
|
||||||
|
a.titleDisp = widget.NewRichTextWithText("Artist")
|
||||||
|
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
|
||||||
|
SizeName: theme.SizeNameHeadingText,
|
||||||
|
}
|
||||||
|
a.container = container.NewBorder(a.titleDisp, nil, nil, nil, layout.NewSpacer())
|
||||||
|
a.loadAsync()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArtistPage) SetPlayAlbumCallback(cb func(string)) {
|
||||||
|
a.OnPlayAlbum = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArtistPage) onPlayAlbum(albumID string) {
|
||||||
|
if a.OnPlayAlbum != nil {
|
||||||
|
a.OnPlayAlbum(albumID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArtistPage) loadAsync() {
|
||||||
|
go func() {
|
||||||
|
artist, err := a.sm.Server.GetArtist(a.artistID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get artist: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name
|
||||||
|
a.titleDisp.Refresh()
|
||||||
|
ag := NewFixedAlbumGrid(artist.Album, a.im.GetAlbumThumbnail, true /*showYear*/)
|
||||||
|
ag.OnPlayAlbum = a.onPlayAlbum
|
||||||
|
a.container.Objects[0] = ag
|
||||||
|
a.container.Refresh()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
a.ExtendBaseWidget(a)
|
||||||
|
return widget.NewSimpleRenderer(a.container)
|
||||||
|
}
|
||||||
+52
-4
@@ -1,12 +1,14 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"supersonic/backend"
|
||||||
"supersonic/ui/widgets"
|
"supersonic/ui/widgets"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,14 +20,26 @@ type Searchable interface {
|
|||||||
OnSearched(string)
|
OnSearched(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CanPlayAlbum interface {
|
||||||
|
SetPlayAlbumCallback(func(albumID string))
|
||||||
|
}
|
||||||
|
|
||||||
type BrowsingPane struct {
|
type BrowsingPane struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
|
app *backend.App
|
||||||
|
|
||||||
|
curPage Page
|
||||||
|
|
||||||
|
forward *widget.Button
|
||||||
|
back *widget.Button
|
||||||
|
history []Page
|
||||||
|
historyIdx int
|
||||||
|
|
||||||
searchBar *widgets.SearchEntry
|
searchBar *widgets.SearchEntry
|
||||||
pendingSearchLock sync.Mutex
|
pendingSearchLock sync.Mutex
|
||||||
pendingSearch bool
|
pendingSearch bool
|
||||||
searchGoroutine bool
|
searchGoroutine bool
|
||||||
curPage Page
|
|
||||||
|
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
@@ -34,24 +48,58 @@ type blankPage struct {
|
|||||||
widget.Separator
|
widget.Separator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrowsingPane() *BrowsingPane {
|
func NewBrowsingPane(app *backend.App) *BrowsingPane {
|
||||||
b := &BrowsingPane{}
|
b := &BrowsingPane{app: app}
|
||||||
b.ExtendBaseWidget(b)
|
b.ExtendBaseWidget(b)
|
||||||
b.searchBar = widgets.NewSearchEntry()
|
b.searchBar = widgets.NewSearchEntry()
|
||||||
b.searchBar.OnTextChanged = b.onSearchTextChanged
|
b.searchBar.OnTextChanged = b.onSearchTextChanged
|
||||||
|
b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack)
|
||||||
|
b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
|
||||||
b.curPage = &blankPage{}
|
b.curPage = &blankPage{}
|
||||||
b.container = container.NewBorder(
|
b.container = container.NewBorder(
|
||||||
container.NewHBox(widgets.NewHSpace(15), b.searchBar),
|
container.NewHBox(b.back, b.forward, b.searchBar),
|
||||||
nil, nil, nil, b.curPage)
|
nil, nil, nil, b.curPage)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BrowsingPane) SetPage(p Page) {
|
func (b *BrowsingPane) SetPage(p Page) {
|
||||||
|
b.addPageToHistory(p)
|
||||||
|
b.doSetPage(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowsingPane) doSetPage(p Page) {
|
||||||
b.curPage = p
|
b.curPage = p
|
||||||
|
if pa, ok := p.(CanPlayAlbum); ok {
|
||||||
|
pa.SetPlayAlbumCallback(func(albumID string) {
|
||||||
|
_ = b.app.PlaybackManager.PlayAlbum(albumID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_, s := p.(Searchable)
|
||||||
|
b.searchBar.Hidden = !s
|
||||||
b.container.Objects[0] = p
|
b.container.Objects[0] = p
|
||||||
b.Refresh()
|
b.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BrowsingPane) addPageToHistory(p Page) {
|
||||||
|
b.history = b.history[:b.historyIdx]
|
||||||
|
b.history = append(b.history, p)
|
||||||
|
b.historyIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowsingPane) GoBack() {
|
||||||
|
if b.historyIdx > 1 {
|
||||||
|
b.historyIdx -= 1
|
||||||
|
b.doSetPage(b.history[b.historyIdx-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowsingPane) GoForward() {
|
||||||
|
if b.historyIdx < len(b.history) {
|
||||||
|
b.historyIdx++
|
||||||
|
b.doSetPage(b.history[b.historyIdx-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BrowsingPane) onSearchTextChanged(text string) {
|
func (b *BrowsingPane) onSearchTextChanged(text string) {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
b.sendSearch("")
|
b.sendSearch("")
|
||||||
|
|||||||
+4
-6
@@ -13,6 +13,7 @@ import (
|
|||||||
type MainWindow struct {
|
type MainWindow struct {
|
||||||
Window fyne.Window
|
Window fyne.Window
|
||||||
|
|
||||||
|
Router Router
|
||||||
BrowsingPane *BrowsingPane
|
BrowsingPane *BrowsingPane
|
||||||
BottomPanel *BottomPanel
|
BottomPanel *BottomPanel
|
||||||
|
|
||||||
@@ -22,9 +23,10 @@ type MainWindow struct {
|
|||||||
func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindow {
|
func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindow {
|
||||||
m := MainWindow{
|
m := MainWindow{
|
||||||
Window: fyneApp.NewWindow(appName),
|
Window: fyneApp.NewWindow(appName),
|
||||||
BrowsingPane: NewBrowsingPane(),
|
BrowsingPane: NewBrowsingPane(app),
|
||||||
BottomPanel: NewBottomPanel(app.Player),
|
BottomPanel: NewBottomPanel(app.Player),
|
||||||
}
|
}
|
||||||
|
m.Router = NewRouter(app, m.BrowsingPane)
|
||||||
m.BottomPanel.SetPlaybackManager(app.PlaybackManager)
|
m.BottomPanel.SetPlaybackManager(app.PlaybackManager)
|
||||||
m.BottomPanel.ImageManager = app.ImageManager
|
m.BottomPanel.ImageManager = app.ImageManager
|
||||||
m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane)
|
m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane)
|
||||||
@@ -38,11 +40,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo
|
|||||||
m.Window.SetTitle(song.Title)
|
m.Window.SetTitle(song.Title)
|
||||||
})
|
})
|
||||||
app.ServerManager.OnServerConnected(func() {
|
app.ServerManager.OnServerConnected(func() {
|
||||||
ap := NewAlbumsPage("Albums", app.LibraryManager, app.ImageManager)
|
m.Router.OpenRoute(AlbumsRoute(backend.AlbumSortRecentlyAdded))
|
||||||
ap.OnPlayAlbum = func(albumID string) {
|
|
||||||
_ = app.PlaybackManager.PlayAlbum(albumID)
|
|
||||||
}
|
|
||||||
m.BrowsingPane.SetPage(ap)
|
|
||||||
})
|
})
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "supersonic/backend"
|
||||||
|
|
||||||
|
type PageName int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Blank PageName = iota
|
||||||
|
Album
|
||||||
|
Albums
|
||||||
|
Artist
|
||||||
|
Artists
|
||||||
|
Playlist
|
||||||
|
Playlists
|
||||||
|
)
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Page PageName
|
||||||
|
Arg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsRoute(sortOrder backend.AlbumSortOrder) Route {
|
||||||
|
return Route{Page: Albums, Arg: string(sortOrder)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArtistRoute(artistID string) Route {
|
||||||
|
return Route{Page: Artist, Arg: artistID}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NavigationHandler interface {
|
||||||
|
SetPage(Page)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
App *backend.App
|
||||||
|
Nav NavigationHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(app *backend.App, nav NavigationHandler) Router {
|
||||||
|
return Router{
|
||||||
|
App: app,
|
||||||
|
Nav: nav,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Router) CreatePage(rte Route) Page {
|
||||||
|
switch rte.Page {
|
||||||
|
case Albums:
|
||||||
|
return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
|
||||||
|
case Artist:
|
||||||
|
return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Router) OpenRoute(rte Route) {
|
||||||
|
r.Nav.SetPage(r.CreatePage(rte))
|
||||||
|
}
|
||||||
+29
-6
@@ -3,6 +3,7 @@ package widgets
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"image"
|
"image"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"supersonic/ui/layout"
|
"supersonic/ui/layout"
|
||||||
|
|
||||||
@@ -62,10 +63,14 @@ type AlbumCard struct {
|
|||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
albumID string
|
albumID string
|
||||||
|
artistID string
|
||||||
title *widget.Label
|
title *widget.Label
|
||||||
artist *widget.Label
|
artist *CustomHyperlink
|
||||||
|
year *widget.Label
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
|
|
||||||
|
showYear bool
|
||||||
|
|
||||||
// updated by AlbumGrid
|
// updated by AlbumGrid
|
||||||
Cover *albumCover
|
Cover *albumCover
|
||||||
|
|
||||||
@@ -74,6 +79,8 @@ type AlbumCard struct {
|
|||||||
ImgLoadCancel context.CancelFunc
|
ImgLoadCancel context.CancelFunc
|
||||||
|
|
||||||
OnPlay func()
|
OnPlay func()
|
||||||
|
OnShowAlbumPage func()
|
||||||
|
OnShowArtistPage func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumCard) MouseIn(*desktop.MouseEvent) {}
|
func (a *AlbumCard) MouseIn(*desktop.MouseEvent) {}
|
||||||
@@ -82,11 +89,13 @@ func (a *AlbumCard) MouseOut() {}
|
|||||||
|
|
||||||
func (a *AlbumCard) MouseMoved(*desktop.MouseEvent) {}
|
func (a *AlbumCard) MouseMoved(*desktop.MouseEvent) {}
|
||||||
|
|
||||||
func NewAlbumCard() *AlbumCard {
|
func NewAlbumCard(showYear bool) *AlbumCard {
|
||||||
a := &AlbumCard{
|
a := &AlbumCard{
|
||||||
title: widget.NewLabel(""),
|
title: widget.NewLabel(""),
|
||||||
artist: widget.NewLabel(""),
|
artist: NewCustomHyperlink(),
|
||||||
|
year: widget.NewLabel(""),
|
||||||
Cover: newAlbumCover(),
|
Cover: newAlbumCover(),
|
||||||
|
showYear: showYear,
|
||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.Cover.OnDoubleTapped = func() {
|
a.Cover.OnDoubleTapped = func() {
|
||||||
@@ -94,8 +103,12 @@ func NewAlbumCard() *AlbumCard {
|
|||||||
a.OnPlay()
|
a.OnPlay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
a.artist.OnTapped = func() {
|
||||||
|
if a.OnShowArtistPage != nil {
|
||||||
|
a.OnShowArtistPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
a.title.Wrapping = fyne.TextTruncate
|
a.title.Wrapping = fyne.TextTruncate
|
||||||
a.artist.Wrapping = fyne.TextTruncate
|
|
||||||
a.title.TextStyle = fyne.TextStyle{Bold: true}
|
a.title.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
a.createContainer()
|
a.createContainer()
|
||||||
@@ -103,8 +116,12 @@ func NewAlbumCard() *AlbumCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumCard) createContainer() {
|
func (a *AlbumCard) createContainer() {
|
||||||
titleArtist := container.New(&layout.VboxCustomPadding{ExtraPad: -16}, a.title, a.artist)
|
var secondLabel fyne.Widget = a.artist
|
||||||
c := container.New(&layout.VboxCustomPadding{ExtraPad: -5}, a.Cover, titleArtist)
|
if a.showYear {
|
||||||
|
secondLabel = a.year
|
||||||
|
}
|
||||||
|
info := container.New(&layout.VboxCustomPadding{ExtraPad: -16}, a.title, secondLabel)
|
||||||
|
c := container.New(&layout.VboxCustomPadding{ExtraPad: -5}, a.Cover, info)
|
||||||
pad := &layout.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
|
pad := &layout.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
|
||||||
a.container = container.New(pad, c)
|
a.container = container.New(pad, c)
|
||||||
}
|
}
|
||||||
@@ -112,13 +129,19 @@ func (a *AlbumCard) createContainer() {
|
|||||||
func (a *AlbumCard) Update(al *subsonic.AlbumID3) {
|
func (a *AlbumCard) Update(al *subsonic.AlbumID3) {
|
||||||
a.title.SetText(al.Name)
|
a.title.SetText(al.Name)
|
||||||
a.artist.SetText(al.Artist)
|
a.artist.SetText(al.Artist)
|
||||||
|
a.year.SetText(strconv.Itoa(al.Year))
|
||||||
a.albumID = al.ID
|
a.albumID = al.ID
|
||||||
|
a.artistID = al.ArtistID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumCard) AlbumID() string {
|
func (a *AlbumCard) AlbumID() string {
|
||||||
return a.albumID
|
return a.albumID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AlbumCard) ArtistID() string {
|
||||||
|
return a.artistID
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AlbumCard) CreateRenderer() fyne.WidgetRenderer {
|
func (a *AlbumCard) CreateRenderer() fyne.WidgetRenderer {
|
||||||
return widget.NewSimpleRenderer(a.container)
|
return widget.NewSimpleRenderer(a.container)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hyperlinkWrapper struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
h *widget.Hyperlink
|
||||||
|
l *widget.Label
|
||||||
|
maxWidth float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHyperlinkWrapper(maxWidth float32) *hyperlinkWrapper {
|
||||||
|
h := &hyperlinkWrapper{
|
||||||
|
h: widget.NewHyperlink("", nil),
|
||||||
|
l: widget.NewLabel(""),
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
}
|
||||||
|
h.h.Wrapping = fyne.TextTruncate
|
||||||
|
h.ExtendBaseWidget(h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hyperlinkWrapper) MinSize() fyne.Size {
|
||||||
|
w := fyne.Min(h.maxWidth, h.l.MinSize().Width)
|
||||||
|
return fyne.NewSize(w, h.h.MinSize().Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hyperlinkWrapper) SetText(text string) {
|
||||||
|
h.h.SetText(text)
|
||||||
|
h.l.SetText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hyperlinkWrapper) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(h.h)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomHyperlink struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
h *hyperlinkWrapper
|
||||||
|
|
||||||
|
OnTapped func()
|
||||||
|
|
||||||
|
container *fyne.Container
|
||||||
|
minSize fyne.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomHyperlink() *CustomHyperlink {
|
||||||
|
c := &CustomHyperlink{
|
||||||
|
h: newHyperlinkWrapper(200),
|
||||||
|
}
|
||||||
|
c.h.h.OnTapped = func() {
|
||||||
|
if c.OnTapped != nil {
|
||||||
|
c.OnTapped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.ExtendBaseWidget(c)
|
||||||
|
c.container = container.NewHBox(c.h, layout.NewSpacer())
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomHyperlink) SetText(text string) {
|
||||||
|
s := widget.NewLabel(text).MinSize()
|
||||||
|
c.h.SetText(text)
|
||||||
|
c.minSize = fyne.NewSize(fyne.Min(c.Size().Width, s.Width), s.Height)
|
||||||
|
c.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomHyperlink) Refresh() {
|
||||||
|
c.container.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomHyperlink) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(c.container)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user