Add favorites view
This commit is contained in:
parent
1c48832ee6
commit
5197ddf26e
6 changed files with 97 additions and 16 deletions
|
|
@ -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"`
|
||||
|
|
|
|||
32
dicli.go
32
dicli.go
|
|
@ -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() {}},
|
||||
|
|
|
|||
38
difm/difm.go
38
difm/difm.go
|
|
@ -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
1
go.mod
|
|
@ -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
2
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue