diff --git a/internal/api/api.go b/internal/api/api.go index 8091a2a..8d42a41 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -16,7 +16,7 @@ func Run(ctx *gctx.Context, client *spotify.Client, args []string) error { if len(args) == 0 { configDir, _ := os.UserConfigDir() if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil { - return tui.DisplayDevices(ctx, client) + return tui.StartTea(ctx, client) } return tui.DisplayMain(ctx, client) } diff --git a/internal/tui/constants.go b/internal/tui/constants.go new file mode 100644 index 0000000..c36bbc2 --- /dev/null +++ b/internal/tui/constants.go @@ -0,0 +1,62 @@ +package tui + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/zmb3/spotify" +) + +/* CONSTANTS */ + +var ( + // P the current tea program + P *tea.Program + // client + Client *spotify.Client + // WindowSize store the size of the terminal window + WindowSize tea.WindowSizeMsg +) + +/* STYLING */ + +// DocStyle styling for viewports +var DocStyle = lipgloss.NewStyle().Margin(0, 2) + +// HelpStyle styling for help context menu +var HelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render + +// ErrStyle provides styling for error messages +var ErrStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#bd534b")).Render + +// AlertStyle provides styling for alert messages +var AlertStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("62")).Render + +type keymap struct { + Radio key.Binding + Enter key.Binding + Rename key.Binding + Delete key.Binding + Back key.Binding + Quit key.Binding +} + +// Keymap reusable key mappings shared across models +var Keymap = keymap{ + Radio: key.NewBinding( + key.WithKeys("ctrl+r"), + key.WithHelp("ctrl+r", "create"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select"), + ), + Back: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "back"), + ), + Quit: key.NewBinding( + key.WithKeys("ctrl+c", "q"), + key.WithHelp("ctrl+c/q", "quit"), + ), +} diff --git a/internal/tui/list.go b/internal/tui/list.go index e2a4123..1f50196 100644 --- a/internal/tui/list.go +++ b/internal/tui/list.go @@ -68,8 +68,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if msg.String() == "backspace" || msg.String() == "q" || msg.String() == "esc" { - DisplayMain(m.ctx, m.client) - return m, tea.Quit + m, err := InitMain(m.ctx, m.client) + if err != nil { + fmt.Println("UH OH") + } + P = tea.NewProgram(m, tea.WithAltScreen()) + if err := P.Start(); err != nil { + return m, tea.Quit + } } if msg.String() == "ctrl+c" { return m, tea.Quit @@ -150,3 +156,33 @@ func DisplayList(ctx *gctx.Context, client *spotify.Client) error { } return nil } + +func InitSavedTracks(ctx *gctx.Context, client *spotify.Client) (tea.Model, error) { + items := []list.Item{} + tracks, err := commands.TrackList(ctx, client, 1) + if err != nil { + return nil, err + } + for _, track := range tracks.Tracks { + items = append(items, item{ + Name: track.Name, + Artist: track.Artists[0], + Duration: track.TimeDuration().Round(time.Second).String(), + ID: track.ID, + }) + } + + m := model{ + list: list.New(items, list.NewDefaultDelegate(), 0, 0), + page: 1, + ctx: ctx, + client: client, + } + m.list.AdditionalShortHelpKeys = func() []key.Binding { + return []key.Binding{ + key.NewBinding(key.WithKeys("ctrl", "r"), key.WithHelp("ctrl+r", "start radio")), + } + } + m.list.Title = "Saved Tracks" + return m, nil +} diff --git a/internal/tui/main.go b/internal/tui/main.go index ab196e7..bd8c282 100644 --- a/internal/tui/main.go +++ b/internal/tui/main.go @@ -64,10 +64,23 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { case spotify.SimplePlaylist: playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist) - PlaylistTracks(m.ctx, m.client, playlist) - return m, tea.Quit + p, err := InitPlaylists(m.ctx, m.client, playlist) + if err != nil { + return m, tea.Quit + } + play := tea.NewProgram(p, tea.WithAltScreen(), tea.WithMouseCellMotion()) + if _, err := play.Run(); err != nil { + return m, tea.Quit + } case *spotify.SavedTrackPage: - DisplayList(m.ctx, m.client) + p, err := InitSavedTracks(m.ctx, m.client) + if err != nil { + return m, tea.Quit + } + play := tea.NewProgram(p, tea.WithAltScreen(), tea.WithMouseCellMotion()) + if _, err := play.Run(); err != nil { + return m, tea.Quit + } return m, tea.Quit } return m, tea.Quit @@ -128,3 +141,32 @@ func DisplayMain(ctx *gctx.Context, client *spotify.Client) error { } return nil } + +func InitMain(ctx *gctx.Context, client *spotify.Client) (tea.Model, error) { + items := []list.Item{} + saved_items, err := commands.TrackList(ctx, client, 1) + items = append(items, mainItem{ + Name: "Saved Tracks", + Desc: fmt.Sprintf("%d saved songs", saved_items.Total), + SpotifyItem: saved_items, + }) + playlists, err := commands.Playlists(ctx, client, 1) + if err != nil { + return nil, err + } + for _, playlist := range playlists.Playlists { + items = append(items, mainItem{ + Name: playlist.Name, + Desc: playlist.Description, + SpotifyItem: playlist, + }) + } + m := mainModel{ + list: list.New(items, list.NewDefaultDelegate(), 0, 0), + page: 1, + ctx: ctx, + client: client, + } + m.list.Title = "GOSPT" + return m, nil +} diff --git a/internal/tui/playlisttracks.go b/internal/tui/playlisttracks.go index 939ff29..6019e60 100644 --- a/internal/tui/playlisttracks.go +++ b/internal/tui/playlisttracks.go @@ -67,8 +67,14 @@ func (m playlistTracksModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if msg.String() == "backspace" || msg.String() == "q" || msg.String() == "esc" { - DisplayMain(m.ctx, m.client) - return m, tea.Quit + m, err := InitMain(m.ctx, m.client) + if err != nil { + fmt.Println("UH OH") + } + P = tea.NewProgram(m, tea.WithAltScreen()) + if err := P.Start(); err != nil { + return m, tea.Quit + } } if msg.String() == "ctrl+c" { return m, tea.Quit @@ -102,6 +108,7 @@ func (m playlistTracksModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: h, v := docStyle.GetFrameSize() m.list.SetSize(msg.Width-h, msg.Height-v) + m.View() } var cmd tea.Cmd @@ -149,3 +156,35 @@ func PlaylistTracks(ctx *gctx.Context, client *spotify.Client, playlist spotify. } return nil } + +func InitPlaylists(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) (tea.Model, error) { + items := []list.Item{} + tracks, err := commands.PlaylistTracks(ctx, client, playlist.ID, 1) + if err != nil { + return nil, err + } + for _, track := range tracks.Tracks { + items = append(items, item{ + Name: track.Track.Name, + Artist: track.Track.Artists[0], + Duration: track.Track.TimeDuration().Round(time.Second).String(), + ID: track.Track.ID, + }) + } + + m := playlistTracksModel{ + list: list.New(items, list.NewDefaultDelegate(), 0, 0), + page: 1, + ctx: ctx, + client: client, + playlist: playlist, + } + m.list.Title = playlist.Name + m.list.AdditionalShortHelpKeys = func() []key.Binding { + return []key.Binding{ + key.NewBinding(key.WithKeys("ctrl", "r"), key.WithHelp("ctrl+r", "start radio")), + } + } + m.View() + return m, err +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go new file mode 100644 index 0000000..678526f --- /dev/null +++ b/internal/tui/tui.go @@ -0,0 +1,34 @@ +package tui + +import ( + "fmt" + "log" + + "gospt/internal/gctx" + + tea "github.com/charmbracelet/bubbletea" + "github.com/zmb3/spotify/v2" +) + +// StartTea the entry point for the UI. Initializes the model. +func StartTea(ctx *gctx.Context, client *spotify.Client) error { + if f, err := tea.LogToFile("debug.log", "help"); err != nil { + return err + } else { + defer func() { + err = f.Close() + if err != nil { + log.Fatal(err) + } + }() + } + m, err := InitMain(ctx, client) + if err != nil { + fmt.Println("UH OH") + } + P = tea.NewProgram(m, tea.WithAltScreen()) + if err := P.Start(); err != nil { + return err + } + return nil +}