Add favorites view

This commit is contained in:
Adriano Caloiaro 2020-03-01 13:38:51 -05:00
parent 1c48832ee6
commit 5197ddf26e
No known key found for this signature in database
GPG key ID: 9FFD0E7601F166AB
6 changed files with 97 additions and 16 deletions

View file

@ -10,6 +10,12 @@ type ChannelItem struct {
Playlist string `json:"playlist"`
}
// FavoriteItem contains a di.fm favorite channel
type FavoriteItem struct {
Name string
PlaylistURL string
}
// CurrentlyPlaying contains the currently playing metadata for a di.fm channel
type CurrentlyPlaying struct {
ChannelID int64 `json:"channel_id"`

View file

@ -51,7 +51,7 @@ func run() {
err := ctx.View.App.
SetRoot(layout, true).
SetFocus(ctx.View.ChannelList).
SetFocus(ctx.View.FavoriteList).
Run()
if err != nil {
@ -63,9 +63,14 @@ func buildUILayout() *tview.Flex {
main := tview.NewFlex()
main.SetDirection(tview.FlexRow)
favsAndChannels := tview.NewFlex().
AddItem(ctx.View.FavoriteList, 0, 1, false).
AddItem(ctx.View.ChannelList, 0, 2, false).
SetDirection(tview.FlexRow)
flex := tview.NewFlex()
flex.
AddItem(ctx.View.ChannelList, 0, 1, false).
AddItem(favsAndChannels, 0, 1, false).
AddItem(ctx.View.NowPlaying, 0, 2, false)
main.
@ -76,16 +81,19 @@ func buildUILayout() *tview.Flex {
}
func configureEventHandling() {
ctx.View.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
focus := ctx.View.App.GetFocus().(*tview.List)
switch event.Rune() {
case 'q':
ctx.View.App.Stop()
case 'j': //scroll down
ctx.View.ChannelList.SetCurrentItem(ctx.View.ChannelList.GetCurrentItem() + 1)
focus.SetCurrentItem(focus.GetCurrentItem() + 1)
case 'k': //scroll up
current := ctx.View.ChannelList.GetCurrentItem()
current := focus.GetCurrentItem()
if current > 0 {
ctx.View.ChannelList.SetCurrentItem(current - 1)
focus.SetCurrentItem(current - 1)
}
case 'p': // pause/resume
app.TogglePause(ctx)
@ -106,6 +114,20 @@ func configureUIComponents() {
})
}
favorites := difm.ListFavorites(ctx)
for _, fav := range favorites {
ctx.View.FavoriteList.AddItem(fav.Name, "", 0, func() {
f := favorites[ctx.View.FavoriteList.GetCurrentItem()]
for _, chn := range channels {
// favorites are prefixed with "DI.fm - <CHANNEL NAME>", shave it off before comparing
if chn.Name == f.Name[8:len(f.Name)] {
app.PlayChannel(&chn, ctx)
return
}
}
})
}
// configure the keybinding view
bindings := []views.UIKeybinding{
views.UIKeybinding{Shortcut: "q", Description: "Quit", Func: func() {}},

View file

@ -15,6 +15,7 @@ import (
"github.com/acaloiaro/dicli/components"
"github.com/acaloiaro/dicli/config"
"github.com/bradfitz/iter"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
ini "gopkg.in/ini.v1"
@ -26,6 +27,7 @@ Listen history: POST /_papi/v1/di/listen_history
Payload: {track_id: 2918701, playlist_id: 63675}
Currently playing (all stations): https://www.di.fm/_papi/v1/di/currently_playing
Skip track: https://www.di.fm/_papi/v1/di/skip_events
Favorites: http://listen.di.fm/public3/favorites.pls?<listen key>
*/
// Authenticate authenticates to the di.fm API with username and password, returning the listen token
@ -123,12 +125,48 @@ func ListChannels() (channels []components.ChannelItem) {
err = json.Unmarshal(body, &channels)
if err != nil {
// TODO: Don't exit here. Once there's a status message area in the app, populate it with the error
log.Panicf("unable to fetch channel list: %e", err)
}
return
}
// ListFavorites lists a user's favorite channels
func ListFavorites(ctx *context.AppContext) (favorites []components.FavoriteItem) {
client := &http.Client{}
url := fmt.Sprintf("%s?%s", "http://listen.di.fm/public3/favorites.pls", ctx.DifmToken)
req, _ := http.NewRequest("GET", url, nil)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
log.Println(string(body))
cfg, err := ini.Load(body)
if err != nil {
// TODO: show an message in a status UI element of the app
fmt.Printf("favorite list parsing failed: %v\n", err.Error())
return
}
sec := "playlist"
numEntries := cfg.Section(sec).Key("NumberOfEntries").MustInt(0)
for i := range iter.N(numEntries) {
// di.fm's PLS keys begin at 1
k := i + 1
favorites = append(favorites, components.FavoriteItem{
Name: cfg.Section(sec).Key(fmt.Sprintf("Title%d", k)).String(),
PlaylistURL: cfg.Section(sec).Key(fmt.Sprintf("File%d", k)).String(),
})
}
return
}
// Stream streams the provided URL using the given di.fm premium token
func Stream(url string, ctx *context.AppContext) (format beep.Format) {
client := &http.Client{}

1
go.mod
View file

@ -4,6 +4,7 @@ go 1.13
require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8
github.com/faiface/beep v1.0.2
github.com/gdamore/tcell v1.3.0
github.com/gizak/termui/v3 v3.1.0

2
go.sum
View file

@ -9,6 +9,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=

View file

@ -9,10 +9,11 @@ import (
)
type AppView struct {
App *tview.Application
ChannelList *tview.List
NowPlaying *NowPlayingView
Keybindings *KeybindingView
App *tview.Application
ChannelList *tview.List
FavoriteList *tview.List
NowPlaying *NowPlayingView
Keybindings *KeybindingView
}
// KeybindingView is a custom view for dispalying the keyboard bindings available to users
@ -31,10 +32,11 @@ type UIKeybinding struct {
// CreateAppView creates the primary application view of di-tui
func CreateAppView() *AppView {
return &AppView{
App: tview.NewApplication(),
ChannelList: createChannelList(),
NowPlaying: newNowPlaying(&components.ChannelItem{}),
Keybindings: createKeybindings(),
App: tview.NewApplication(),
ChannelList: createChannelList(),
FavoriteList: createFavoriteList(),
NowPlaying: newNowPlaying(&components.ChannelItem{}),
Keybindings: createKeybindings(),
}
}
@ -63,13 +65,13 @@ func (n *NowPlayingView) Draw(screen tcell.Screen) {
n.Box.Draw(screen)
x, y, width, _ := n.GetInnerRect()
line := fmt.Sprintf("%s[white] %s", "Channel:", n.Channel.Name)
line := fmt.Sprintf("%s[white] %s", "Channel:", n.Channel.Name)
tview.Print(screen, line, x, y, width, tview.AlignLeft, tcell.ColorBlue)
line = fmt.Sprintf("%s[white] %s", "Artist:", n.Artist)
line = fmt.Sprintf("%s[white] %s", "Artist:", n.Artist)
tview.Print(screen, line, x, y+1, width, tview.AlignLeft, tcell.ColorBlue)
line = fmt.Sprintf("%s[white] %s", "Track:", n.Track)
line = fmt.Sprintf("%s[white] %s", "Track:", n.Track)
tview.Print(screen, line, x, y+2, width, tview.AlignLeft, tcell.ColorBlue)
}
@ -97,6 +99,16 @@ func createChannelList() *tview.List {
return list
}
func createFavoriteList() *tview.List {
list := tview.NewList()
list.
ShowSecondaryText(false).
SetBorder(true).
SetTitle(" Favorites ")
return list
}
func createKeybindings() *KeybindingView {
kbv := &KeybindingView{Box: tview.NewBox()}
kbv.