From 0a26149cde70026a6e25d3028120f09023671743 Mon Sep 17 00:00:00 2001 From: aditya-K2 Date: Fri, 14 Apr 2023 02:56:16 +0530 Subject: [PATCH] Implement ArtistsView and ArtistView --- spt/auth.go | 1 + spt/get.go | 58 ++++++++++++++++++++++++++++++++ ui/app.go | 7 ++-- ui/view.go | 2 ++ ui/view_artist.go | 83 +++++++++++++++++++++++++++++++++++++++++++++- ui/view_artists.go | 54 ++++++++++++++++++++++++++++++ ui/view_top.go | 2 +- 7 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 ui/view_artists.go diff --git a/spt/auth.go b/spt/auth.go index 316c164..d3f205c 100644 --- a/spt/auth.go +++ b/spt/auth.go @@ -30,6 +30,7 @@ var ( sptauth.ScopeUserLibraryModify, sptauth.ScopeUserLibraryRead, sptauth.ScopeUserReadPrivate, + sptauth.ScopeUserFollowRead, sptauth.ScopeUserReadCurrentlyPlaying, sptauth.ScopeUserModifyPlaybackState, sptauth.ScopeUserReadRecentlyPlayed, diff --git a/spt/get.go b/spt/get.go index cf64cdd..04134f0 100644 --- a/spt/get.go +++ b/spt/get.go @@ -9,6 +9,12 @@ import ( var ( topTracksLimit = 15 + albumtypes = []spotify.AlbumType{ + spotify.AlbumTypeAlbum, + spotify.AlbumTypeSingle, + spotify.AlbumTypeAppearsOn, + spotify.AlbumTypeCompilation, + } ) type Playlist struct { @@ -23,6 +29,7 @@ type Album struct { } type SavedAlbums []spotify.SavedAlbum +type FollowedArtists []spotify.FullArtist type UserPlaylists []spotify.SimplePlaylist type LikedSongs []spotify.SavedTrack @@ -214,6 +221,41 @@ func CurrentUserSavedTracks(errHandler func(err error)) (*LikedSongs, error) { } } +func CurrentUserFollowedArtists(errHandler func(error)) (*FollowedArtists, error) { + // TODO: Check if this is the proper implementation + _a := make(FollowedArtists, 0) + artists := &_a + if ar, err := Client.CurrentUsersFollowedArtists(ctx()); err != nil { + return artists, err + } else { + ap := spotify.ID("") + addArtists := func() { + _a = append(_a, ar.Artists...) + ap = _a[len(_a)-1].ID + } + addArtists() + go func() { + for { + if len(ar.Artists) == 0 { + errHandler(nil) + } + if ar, err = Client.CurrentUsersFollowedArtists(ctx(), spotify.After(string(ap))); err != nil { + if err == spotify.ErrNoMorePages { + errHandler(nil) + break + } else { + errHandler(err) + break + } + } else { + addArtists() + } + } + }() + return artists, nil + } +} + func RecentlyPlayed() ([]spotify.RecentlyPlayedItem, error) { return Client.PlayerRecentlyPlayedOpt(ctx(), &spotify.RecentlyPlayedOptions{Limit: 50}) } @@ -231,3 +273,19 @@ func GetTopArtists() ([]spotify.FullArtist, error) { c, err := Client.CurrentUsersTopArtists(ctx(), spotify.Limit(topTracksLimit)) return c.Artists, err } + +func GetArtistTopTracks(artistID spotify.ID) ([]spotify.FullTrack, error) { + c, err := Client.CurrentUser(ctx()) + if err != nil { + return []spotify.FullTrack{}, err + } + return Client.GetArtistsTopTracks(ctx(), artistID, c.Country) +} + +func GetArtistAlbums(artistID spotify.ID) ([]spotify.SimpleAlbum, error) { + c, err := Client.GetArtistAlbums(ctx(), artistID, albumtypes) + if err != nil { + return []spotify.SimpleAlbum{}, err + } + return c.Albums, nil +} diff --git a/ui/app.go b/ui/app.go index 8ac87c0..0b1b04d 100644 --- a/ui/app.go +++ b/ui/app.go @@ -1,7 +1,6 @@ package ui import ( - "fmt" "time" "github.com/gdamore/tcell/v2" @@ -58,7 +57,11 @@ func NewApplication() *Application { App.SetFocus(Main.Table) return nil }, nil)}, - {"Artists", NewAction(func(e *tcell.EventKey) *tcell.EventKey { fmt.Println("Artists"); return nil }, nil)}, + {"Artists", NewAction(func(e *tcell.EventKey) *tcell.EventKey { + SetCurrentView(artistsView) + App.SetFocus(Main.Table) + return nil + }, nil)}, {"Liked Songs", NewAction(func(e *tcell.EventKey) *tcell.EventKey { SetCurrentView(likedSongsView) App.SetFocus(Main.Table) diff --git a/ui/view.go b/ui/view.go index 0470c6b..40cbf20 100644 --- a/ui/view.go +++ b/ui/view.go @@ -10,6 +10,8 @@ var ( likedSongsView = &LikedSongsView{} recentlyPlayedView = &RecentlyPlayedView{} topTracksView = &TopTracksView{} + artistView = &ArtistView{} + artistsView = &ArtistsView{} ) type View interface { diff --git a/ui/view_artist.go b/ui/view_artist.go index cbbb73c..ed39e89 100644 --- a/ui/view_artist.go +++ b/ui/view_artist.go @@ -1,8 +1,89 @@ package ui -import "github.com/zmb3/spotify/v2" +import ( + "github.com/aditya-K2/gspt/spt" + "github.com/gdamore/tcell/v2" + "github.com/zmb3/spotify/v2" +) type ArtistView struct { + *DefaultViewNone + artistID *spotify.ID topTracks []spotify.FullTrack albums []spotify.SimpleAlbum } + +func (a *ArtistView) SetArtist(id *spotify.ID) { + a.artistID = id +} + +func (a *ArtistView) RefreshState() { + if a.artistID != nil { + topTracks, err := spt.GetArtistTopTracks(*a.artistID) + if err != nil { + SendNotification("Error retrieving Artist Top Tracks: " + err.Error()) + return + } + a.topTracks = topTracks + albums, err := spt.GetArtistAlbums(*a.artistID) + if err != nil { + SendNotification("Error retrieving Artist Albums: " + err.Error()) + return + } + a.albums = albums + } +} + +func (a *ArtistView) Content() func() [][]Content { + return func() [][]Content { + c := make([][]Content, 0) + c = append(c, []Content{{"Artist Albums: ", NotSelectableStyle}}) + for _, v := range a.albums { + c = append(c, []Content{ + {Content: v.Name, Style: AlbumStyle}, + {Content: v.Artists[0].Name, Style: ArtistStyle}, + {Content: v.ReleaseDate, Style: TimeStyle}, + }) + } + c = append(c, []Content{{"Artist Top Tracks:", NotSelectableStyle}}) + for _, v := range a.topTracks { + c = append(c, []Content{ + {Content: v.Name, Style: TrackStyle}, + {Content: v.Artists[0].Name, Style: ArtistStyle}, + {Content: v.Album.Name, Style: AlbumStyle}, + }) + } + return c + } +} + +func (a *ArtistView) ExternalInputCapture() func(e *tcell.EventKey) *tcell.EventKey { + return func(e *tcell.EventKey) *tcell.EventKey { + if e.Key() == tcell.KeyCtrlP { + r, _ := Ui.Main.Table.GetSelection() + if r > 0 { + if r < (len(a.albums) + 1) { + if err := spt.PlayContext(&a.albums[r-1].URI); err != nil { + SendNotification(err.Error()) + } + } + } + } + if e.Key() == tcell.KeyEnter { + r, _ := Ui.Main.Table.GetSelection() + if r > 0 { + if r < (len(a.albums) + 1) { + albumView.SetAlbum(a.albums[r-1].Name, &a.albums[r-1].ID) + SetCurrentView(albumView) + } else if r != len(a.albums)+1 { + if err := spt.PlaySong(a.topTracks[r-2-len(a.albums)].URI); err != nil { + SendNotification(err.Error()) + } + } + } + } + return e + } +} + +func (a *ArtistView) Name() string { return "AlbumsView" } diff --git a/ui/view_artists.go b/ui/view_artists.go new file mode 100644 index 0000000..ca57059 --- /dev/null +++ b/ui/view_artists.go @@ -0,0 +1,54 @@ +package ui + +import ( + "github.com/aditya-K2/gspt/spt" + "github.com/gdamore/tcell/v2" +) + +type ArtistsView struct { + *DefaultViewNone + followedArtists *spt.FollowedArtists +} + +func (a *ArtistsView) Content() func() [][]Content { + return func() [][]Content { + c := make([][]Content, 0) + if a.followedArtists == nil { + msg := SendNotificationWithChan("Loading Artists from your Library...") + fa, err := spt.CurrentUserFollowedArtists(func(err error) { + go func() { + if err != nil { + msg <- err.Error() + } else { + msg <- "Artists loaded Succesfully!" + } + }() + }) + if err != nil { + SendNotification(err.Error()) + } + a.followedArtists = fa + } + for _, v := range *a.followedArtists { + c = append(c, []Content{ + {Content: v.Name, Style: ArtistStyle}, + // {Content: v.Genres[0], Style: AlbumStyle}, + }) + } + return c + } +} + +func (a *ArtistsView) ExternalInputCapture() func(e *tcell.EventKey) *tcell.EventKey { + return func(e *tcell.EventKey) *tcell.EventKey { + if e.Key() == tcell.KeyEnter { + r, _ := Ui.Main.Table.GetSelection() + artistView.SetArtist(&(*a.followedArtists)[r].ID) + artistView.RefreshState() + SetCurrentView(artistView) + } + return e + } +} + +func (a *ArtistsView) Name() string { return "ArtistsView" } diff --git a/ui/view_top.go b/ui/view_top.go index 08dc33a..3d6073e 100644 --- a/ui/view_top.go +++ b/ui/view_top.go @@ -69,4 +69,4 @@ func (a *TopTracksView) ExternalInputCapture() func(e *tcell.EventKey) *tcell.Ev } } -func (a *TopTracksView) Name() string { return "AlbumsView" } +func (a *TopTracksView) Name() string { return "TopTracksView" }