another big refactor to make code more manageable
This commit is contained in:
@@ -14,11 +14,11 @@ type AlbumIterator interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LibraryManager struct {
|
type LibraryManager struct {
|
||||||
s *subsonic.Client
|
s *ServerManager
|
||||||
albumCache gcache.Cache
|
albumCache gcache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLibraryManager(s *subsonic.Client) *LibraryManager {
|
func NewLibraryManager(s *ServerManager) *LibraryManager {
|
||||||
cache := gcache.New(250).LRU().Build()
|
cache := gcache.New(250).LRU().Build()
|
||||||
return &LibraryManager{
|
return &LibraryManager{
|
||||||
s: s,
|
s: s,
|
||||||
@@ -52,7 +52,7 @@ func (l *LibraryManager) GetAlbum(id string) (*subsonic.AlbumID3, error) {
|
|||||||
return a.(*subsonic.AlbumID3), nil
|
return a.(*subsonic.AlbumID3), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a, err := l.s.GetAlbum(id)
|
a, err := l.s.Server.GetAlbum(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ func (l *LibraryManager) newBaseIter(listType string) *baseIter {
|
|||||||
return &baseIter{
|
return &baseIter{
|
||||||
listType: listType,
|
listType: listType,
|
||||||
l: l,
|
l: l,
|
||||||
s: l.s,
|
s: l.s.Server,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"supersonic/player"
|
||||||
|
|
||||||
|
"github.com/20after4/configdir"
|
||||||
|
"github.com/zalando/go-keyring"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppName = "supersonic"
|
||||||
|
configFile = "config.toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoServers = errors.New("no servers set up")
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Config *Config
|
||||||
|
ServerManager *ServerManager
|
||||||
|
ImageManager *ImageManager
|
||||||
|
LibraryManager *LibraryManager
|
||||||
|
PlaybackManager *PlaybackManager
|
||||||
|
Player *player.Player
|
||||||
|
|
||||||
|
bgrndCtx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartupApp() (*App, error) {
|
||||||
|
a := &App{}
|
||||||
|
a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
a.readConfig()
|
||||||
|
|
||||||
|
if err := a.initMPV(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ServerManager = NewServerManager()
|
||||||
|
a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player)
|
||||||
|
a.LibraryManager = NewLibraryManager(a.ServerManager)
|
||||||
|
a.ImageManager = NewImageManager(a.ServerManager, configdir.LocalCache(AppName))
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) readConfig() {
|
||||||
|
configdir.MakePath(configdir.LocalConfig(AppName))
|
||||||
|
cfg, err := ReadConfigFile(configPath())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading app config file: %v", err)
|
||||||
|
cfg = DefaultConfig()
|
||||||
|
}
|
||||||
|
a.Config = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) initMPV() error {
|
||||||
|
p := player.NewWithClientName(AppName)
|
||||||
|
if err := p.Init(); err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize mpv player: %s", err.Error())
|
||||||
|
}
|
||||||
|
a.Player = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) LoginToDefaultServer() error {
|
||||||
|
serverCfg := a.Config.GetDefaultServer()
|
||||||
|
if serverCfg == nil {
|
||||||
|
return ErrNoServers
|
||||||
|
}
|
||||||
|
pass, err := keyring.Get(AppName, serverCfg.ID.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading keyring credentials: %v", err)
|
||||||
|
}
|
||||||
|
return a.ServerManager.ConnectToServer(serverCfg, pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Shutdown() {
|
||||||
|
a.cancel()
|
||||||
|
a.Player.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func configPath() string {
|
||||||
|
return path.Join(configdir.LocalConfig(AppName), configFile)
|
||||||
|
}
|
||||||
+14
-11
@@ -10,24 +10,23 @@ import (
|
|||||||
|
|
||||||
"github.com/20after4/configdir"
|
"github.com/20after4/configdir"
|
||||||
"github.com/bluele/gcache"
|
"github.com/bluele/gcache"
|
||||||
subsonic "github.com/dweymouth/go-subsonic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImageManager struct {
|
type ImageManager struct {
|
||||||
s *subsonic.Client
|
s *ServerManager
|
||||||
cacheDir string
|
baseCacheDir string
|
||||||
thumbnailCache gcache.Cache
|
thumbnailCache gcache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageManager(s *subsonic.Client, cacheDir string) *ImageManager {
|
func NewImageManager(s *ServerManager, baseCacheDir string) *ImageManager {
|
||||||
cache := gcache.New(100).LRU().Build()
|
cache := gcache.New(100).LRU().Build()
|
||||||
if err := configdir.MakePath(cacheDir); err != nil {
|
if err := configdir.MakePath(baseCacheDir); err != nil {
|
||||||
log.Println("failed to create album cover cache dir")
|
log.Println("failed to create album cover cache dir")
|
||||||
cacheDir = ""
|
baseCacheDir = ""
|
||||||
}
|
}
|
||||||
return &ImageManager{
|
return &ImageManager{
|
||||||
s: s,
|
s: s,
|
||||||
cacheDir: cacheDir,
|
baseCacheDir: baseCacheDir,
|
||||||
thumbnailCache: cache,
|
thumbnailCache: cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,8 +40,8 @@ func (i *ImageManager) GetAlbumThumbnail(albumID string) (image.Image, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// on disc cache
|
// on disc cache
|
||||||
path := filepath.Join(i.cacheDir, fmt.Sprintf("%s.jpg", albumID))
|
path := filepath.Join(i.coverCacheDir(), fmt.Sprintf("%s.jpg", albumID))
|
||||||
if i.cacheDir != "" {
|
if i.coverCacheDir() != "" {
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
// serve image from on-disc cache
|
// serve image from on-disc cache
|
||||||
// TODO: image may have changed on server.
|
// TODO: image may have changed on server.
|
||||||
@@ -58,11 +57,11 @@ func (i *ImageManager) GetAlbumThumbnail(albumID string) (image.Image, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch from server
|
// fetch from server
|
||||||
img, err := i.s.GetCoverArt(albumID, map[string]string{"size": "300"})
|
img, err := i.s.Server.GetCoverArt(albumID, map[string]string{"size": "300"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if i.cacheDir != "" {
|
if i.coverCacheDir() != "" {
|
||||||
if f, err := os.Create(path); err == nil {
|
if f, err := os.Create(path); err == nil {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := jpeg.Encode(f, img, nil /*options*/); err != nil {
|
if err := jpeg.Encode(f, img, nil /*options*/); err != nil {
|
||||||
@@ -73,3 +72,7 @@ func (i *ImageManager) GetAlbumThumbnail(albumID string) (image.Image, error) {
|
|||||||
i.thumbnailCache.Set(albumID, img)
|
i.thumbnailCache.Set(albumID, img)
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ImageManager) coverCacheDir() string {
|
||||||
|
return configdir.LocalCache(i.baseCacheDir, i.s.ServerID.String(), "covers")
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type PlaybackManager struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelPollPos context.CancelFunc
|
cancelPollPos context.CancelFunc
|
||||||
pollingTick *time.Ticker
|
pollingTick *time.Ticker
|
||||||
client *subsonic.Client
|
sm *ServerManager
|
||||||
player *player.Player
|
player *player.Player
|
||||||
|
|
||||||
playQueue []*subsonic.Child
|
playQueue []*subsonic.Child
|
||||||
@@ -21,10 +21,10 @@ type PlaybackManager struct {
|
|||||||
onPlayTimeUpdate []func(float64, float64)
|
onPlayTimeUpdate []func(float64, float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlaybackManager(ctx context.Context, cli *subsonic.Client, p *player.Player) *PlaybackManager {
|
func NewPlaybackManager(ctx context.Context, s *ServerManager, p *player.Player) *PlaybackManager {
|
||||||
pm := &PlaybackManager{
|
pm := &PlaybackManager{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
client: cli,
|
sm: s,
|
||||||
player: p,
|
player: p,
|
||||||
}
|
}
|
||||||
p.OnTrackChange(func(tracknum int64) {
|
p.OnTrackChange(func(tracknum int64) {
|
||||||
@@ -77,7 +77,7 @@ func (p *PlaybackManager) OnPlayTimeUpdate(cb func(float64, float64)) {
|
|||||||
|
|
||||||
// Loads the specified album into the play queue.
|
// Loads the specified album into the play queue.
|
||||||
func (p *PlaybackManager) LoadAlbum(albumID string, appendToQueue bool) error {
|
func (p *PlaybackManager) LoadAlbum(albumID string, appendToQueue bool) error {
|
||||||
album, err := p.client.GetAlbum(albumID)
|
album, err := p.sm.Server.GetAlbum(albumID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ func (p *PlaybackManager) LoadAlbum(albumID string, appendToQueue bool) error {
|
|||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
}
|
}
|
||||||
for _, song := range album.Song {
|
for _, song := range album.Song {
|
||||||
url, err := p.client.GetStreamURL(song.ID, map[string]string{})
|
url, err := p.sm.Server.GetStreamURL(song.ID, map[string]string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
subsonic "github.com/dweymouth/go-subsonic"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerManager struct {
|
||||||
|
ServerID uuid.UUID
|
||||||
|
Server *subsonic.Client
|
||||||
|
|
||||||
|
onServerConnected []func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerManager() *ServerManager {
|
||||||
|
return &ServerManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerManager) ConnectToServer(conf *ServerConfig, password string) error {
|
||||||
|
cli := &subsonic.Client{
|
||||||
|
Client: &http.Client{},
|
||||||
|
BaseUrl: conf.Hostname,
|
||||||
|
User: conf.Username,
|
||||||
|
ClientName: "supersonic",
|
||||||
|
}
|
||||||
|
if err := cli.Authenticate(password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Server = cli
|
||||||
|
s.ServerID = conf.ID
|
||||||
|
for _, cb := range s.onServerConnected {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerManager) OnServerConnected(cb func()) {
|
||||||
|
s.onServerConnected = append(s.onServerConnected, cb)
|
||||||
|
}
|
||||||
@@ -1,21 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"path"
|
"path"
|
||||||
"supersonic/backend"
|
"supersonic/backend"
|
||||||
"supersonic/player"
|
|
||||||
"supersonic/ui"
|
"supersonic/ui"
|
||||||
"supersonic/ui/widgets"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"github.com/20after4/configdir"
|
"github.com/20after4/configdir"
|
||||||
"github.com/dweymouth/go-subsonic"
|
|
||||||
"github.com/zalando/go-keyring"
|
"github.com/zalando/go-keyring"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,88 +21,43 @@ func configPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// TODO: organize this whole file better. Move some stuff to mainwindow?
|
myApp, err := backend.StartupApp()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
p := player.NewWithClientName(appname)
|
|
||||||
if err := p.Init(); err != nil {
|
|
||||||
log.Fatalf("failed to initialize mpv player: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
configdir.MakePath(configdir.LocalConfig(appname))
|
|
||||||
|
|
||||||
cfg, err := backend.ReadConfigFile(configPath())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading app config file: %v", err)
|
log.Fatalf("fatal startup error: %v", err.Error())
|
||||||
cfg = backend.DefaultConfig()
|
|
||||||
}
|
}
|
||||||
server := cfg.GetDefaultServer()
|
|
||||||
|
|
||||||
myApp := app.New()
|
fyneApp := app.New()
|
||||||
mainWindow := ui.NewMainWindow(myApp, appname, p)
|
mainWindow := ui.NewMainWindow(fyneApp, appname, myApp)
|
||||||
|
defaultServer := myApp.Config.GetDefaultServer()
|
||||||
if server == nil {
|
if defaultServer == nil {
|
||||||
d := widgets.NewAddServerForm("Connect to Server")
|
mainWindow.PromptForFirstServer(func(nick, host, user, pass string) {
|
||||||
pop := widget.NewModalPopUp(d, mainWindow.Canvas())
|
server := myApp.Config.AddServer(nick, host, user)
|
||||||
d.OnSubmit = func() {
|
err := keyring.Set(appname, server.ID.String(), pass)
|
||||||
pop.Hide()
|
|
||||||
server = cfg.AddServer(d.Nickname, d.Host, d.Username)
|
|
||||||
err := keyring.Set(appname, server.ID.String(), d.Password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error setting keyring credentials: %v", err)
|
log.Printf("error setting keyring credentials: %v", err)
|
||||||
|
// TODO: handle?
|
||||||
}
|
}
|
||||||
setupServer(ctx, mainWindow, p, server)
|
setupServer(myApp, server)
|
||||||
}
|
})
|
||||||
pop.Show()
|
|
||||||
} else {
|
} else {
|
||||||
setupServer(ctx, mainWindow, p, server)
|
setupServer(myApp, defaultServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.Show()
|
mainWindow.Show()
|
||||||
myApp.Run()
|
fyneApp.Run()
|
||||||
cfg.WriteConfigFile(configPath())
|
|
||||||
|
myApp.Config.WriteConfigFile(configPath())
|
||||||
|
myApp.Shutdown()
|
||||||
|
|
||||||
cancel()
|
|
||||||
p.Destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupServer(ctx context.Context, myWindow ui.MainWindow, p *player.Player, server *backend.ServerConfig) {
|
func setupServer(app *backend.App, server *backend.ServerConfig) {
|
||||||
pass, err := keyring.Get(appname, server.ID.String())
|
pass, err := keyring.Get(appname, server.ID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error reading keyring credentials: %v", err)
|
log.Printf("error getting password from keyring: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := app.ServerManager.ConnectToServer(server, pass); err != nil {
|
||||||
s := &subsonic.Client{
|
log.Printf("error connecting to server: %v", err)
|
||||||
Client: &http.Client{},
|
// TODO: surface error to user
|
||||||
BaseUrl: server.Hostname,
|
|
||||||
User: server.Username,
|
|
||||||
ClientName: appname,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Authenticate(pass); err != nil {
|
|
||||||
// TODO: error dialog
|
|
||||||
fmt.Printf("error authenticating: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lm := backend.NewLibraryManager(s)
|
|
||||||
pm := backend.NewPlaybackManager(ctx, s, p)
|
|
||||||
im := backend.NewImageManager(s, configdir.LocalCache(appname, server.ID.String(), "covers"))
|
|
||||||
myWindow.BottomPanel.ImageManager = im
|
|
||||||
|
|
||||||
myWindow.BottomPanel.SetPlaybackManager(pm)
|
|
||||||
pm.OnSongChange(func(song *subsonic.Child) {
|
|
||||||
if song == nil {
|
|
||||||
myWindow.SetTitle("gomuse")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
myWindow.SetTitle(song.Title)
|
|
||||||
})
|
|
||||||
|
|
||||||
ag := ui.NewAlbumGrid(lm.RecentlyAddedIter(), im.GetAlbumThumbnail)
|
|
||||||
ag.OnPlayAlbum = func(albumID string) {
|
|
||||||
_ = pm.PlayAlbum(albumID)
|
|
||||||
}
|
|
||||||
myWindow.SetContent(container.NewBorder(nil, myWindow.BottomPanel, nil, nil, ag))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ type AlbumGrid struct {
|
|||||||
fetching bool
|
fetching bool
|
||||||
done bool
|
done bool
|
||||||
|
|
||||||
imageFetcher func(string) (image.Image, error)
|
imageFetcher ImageFetcher
|
||||||
OnPlayAlbum func(string)
|
OnPlayAlbum func(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -59,8 +59,8 @@ type BottomPanel struct {
|
|||||||
|
|
||||||
playbackManager *backend.PlaybackManager
|
playbackManager *backend.PlaybackManager
|
||||||
|
|
||||||
nowPlaying *widgets.NowPlayingCard
|
NowPlaying *widgets.NowPlayingCard
|
||||||
controls *widgets.PlayerControls
|
Controls *widgets.PlayerControls
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,31 +70,31 @@ func NewBottomPanel(p *player.Player) *BottomPanel {
|
|||||||
bp := &BottomPanel{}
|
bp := &BottomPanel{}
|
||||||
bp.ExtendBaseWidget(bp)
|
bp.ExtendBaseWidget(bp)
|
||||||
p.OnPaused(func() {
|
p.OnPaused(func() {
|
||||||
bp.controls.SetPlaying(false)
|
bp.Controls.SetPlaying(false)
|
||||||
})
|
})
|
||||||
p.OnPlaying(func() {
|
p.OnPlaying(func() {
|
||||||
bp.controls.SetPlaying(true)
|
bp.Controls.SetPlaying(true)
|
||||||
})
|
})
|
||||||
p.OnStopped(func() {
|
p.OnStopped(func() {
|
||||||
bp.controls.SetPlaying(false)
|
bp.Controls.SetPlaying(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
bp.nowPlaying = widgets.NewNowPlayingCard()
|
bp.NowPlaying = widgets.NewNowPlayingCard()
|
||||||
bp.controls = widgets.NewPlayerControls()
|
bp.Controls = widgets.NewPlayerControls()
|
||||||
bp.controls.OnPlayPause(func() {
|
bp.Controls.OnPlayPause(func() {
|
||||||
p.PlayPause()
|
p.PlayPause()
|
||||||
})
|
})
|
||||||
bp.controls.OnSeekNext(func() {
|
bp.Controls.OnSeekNext(func() {
|
||||||
p.SeekNext()
|
p.SeekNext()
|
||||||
})
|
})
|
||||||
bp.controls.OnSeekPrevious(func() {
|
bp.Controls.OnSeekPrevious(func() {
|
||||||
p.SeekBackOrPrevious()
|
p.SeekBackOrPrevious()
|
||||||
})
|
})
|
||||||
bp.controls.OnSeek(func(f float64) {
|
bp.Controls.OnSeek(func(f float64) {
|
||||||
p.Seek(fmt.Sprintf("%d", int(f*100)), player.SeekAbsolutePercent)
|
p.Seek(fmt.Sprintf("%d", int(f*100)), player.SeekAbsolutePercent)
|
||||||
})
|
})
|
||||||
|
|
||||||
bp.container = container.New(newBottomPanelLayout(500, bp.nowPlaying, bp.controls, nil), bp.nowPlaying, bp.controls)
|
bp.container = container.New(newBottomPanelLayout(500, bp.NowPlaying, bp.Controls, nil), bp.NowPlaying, bp.Controls)
|
||||||
return bp
|
return bp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,20 +103,20 @@ func (bp *BottomPanel) SetPlaybackManager(pm *backend.PlaybackManager) {
|
|||||||
pm.OnSongChange(bp.onSongChange)
|
pm.OnSongChange(bp.onSongChange)
|
||||||
pm.OnPlayTimeUpdate(func(cur, total float64) {
|
pm.OnPlayTimeUpdate(func(cur, total float64) {
|
||||||
if !pm.IsSeeking() {
|
if !pm.IsSeeking() {
|
||||||
bp.controls.UpdatePlayTime(cur, total)
|
bp.Controls.UpdatePlayTime(cur, total)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *BottomPanel) onSongChange(song *subsonic.Child) {
|
func (bp *BottomPanel) onSongChange(song *subsonic.Child) {
|
||||||
if song == nil {
|
if song == nil {
|
||||||
bp.nowPlaying.Update("", "", "", nil)
|
bp.NowPlaying.Update("", "", "", nil)
|
||||||
} else {
|
} else {
|
||||||
var im image.Image
|
var im image.Image
|
||||||
if bp.ImageManager != nil {
|
if bp.ImageManager != nil {
|
||||||
im, _ = bp.ImageManager.GetAlbumThumbnail(song.AlbumID)
|
im, _ = bp.ImageManager.GetAlbumThumbnail(song.AlbumID)
|
||||||
}
|
}
|
||||||
bp.nowPlaying.Update(song.Title, song.Artist, song.Album, im)
|
bp.NowPlaying.Update(song.Title, song.Artist, song.Album, im)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-4
@@ -1,11 +1,14 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"supersonic/player"
|
"supersonic/backend"
|
||||||
|
"supersonic/ui/widgets"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/dweymouth/go-subsonic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainWindow struct {
|
type MainWindow struct {
|
||||||
@@ -14,17 +17,43 @@ type MainWindow struct {
|
|||||||
BottomPanel *BottomPanel
|
BottomPanel *BottomPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainWindow(app fyne.App, appName string, p *player.Player) MainWindow {
|
func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindow {
|
||||||
m := MainWindow{
|
m := MainWindow{
|
||||||
Window: app.NewWindow(appName),
|
Window: fyneApp.NewWindow(appName),
|
||||||
BottomPanel: NewBottomPanel(p),
|
BottomPanel: NewBottomPanel(app.Player),
|
||||||
}
|
}
|
||||||
|
m.BottomPanel.SetPlaybackManager(app.PlaybackManager)
|
||||||
|
m.BottomPanel.ImageManager = app.ImageManager
|
||||||
c := container.NewBorder(nil, m.BottomPanel, nil, nil, &layout.Spacer{})
|
c := container.NewBorder(nil, m.BottomPanel, nil, nil, &layout.Spacer{})
|
||||||
m.Window.SetContent(c)
|
m.Window.SetContent(c)
|
||||||
m.Window.Resize(fyne.NewSize(1000, 800))
|
m.Window.Resize(fyne.NewSize(1000, 800))
|
||||||
|
app.PlaybackManager.OnSongChange(func(song *subsonic.Child) {
|
||||||
|
if song == nil {
|
||||||
|
m.Window.SetTitle(appName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Window.SetTitle(song.Title)
|
||||||
|
})
|
||||||
|
app.ServerManager.OnServerConnected(func() {
|
||||||
|
ag := NewAlbumGrid(app.LibraryManager.RecentlyAddedIter(), app.ImageManager.GetAlbumThumbnail)
|
||||||
|
ag.OnPlayAlbum = func(albumID string) {
|
||||||
|
_ = app.PlaybackManager.PlayAlbum(albumID)
|
||||||
|
}
|
||||||
|
m.Window.SetContent(container.NewBorder(nil, m.BottomPanel, nil, nil, ag))
|
||||||
|
})
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string)) {
|
||||||
|
d := widgets.NewAddServerForm("Connect to Server")
|
||||||
|
pop := widget.NewModalPopUp(d, m.Canvas())
|
||||||
|
d.OnSubmit = func() {
|
||||||
|
pop.Hide()
|
||||||
|
cb(d.Nickname, d.Host, d.Username, d.Password)
|
||||||
|
}
|
||||||
|
pop.Show()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MainWindow) Show() {
|
func (m *MainWindow) Show() {
|
||||||
m.Window.Show()
|
m.Window.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user