Skip to content

Commit

Permalink
functional playlist list
Browse files Browse the repository at this point in the history
  • Loading branch information
danbulant committed Oct 15, 2024
1 parent 2963da1 commit 811ac8e
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 10 deletions.
6 changes: 5 additions & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use futures_util::lock::Mutex;
use librespot_core::Session;
use librespot_oauth::OAuthToken;
use reqwest::StatusCode;
use rspotify::model::PrivateUser;
use rspotify::model::{Page, PrivateUser, SimplifiedPlaylist, UserId};
use rspotify::prelude::*;
use rspotify::{AuthCodeSpotify, ClientError, ClientResult, Config, Token};
use rspotify::http::HttpError;
Expand Down Expand Up @@ -108,6 +108,10 @@ impl SpotifyContext {
self.api_with_retry(|api| api.current_user()).await.ok_or(())
}

pub async fn current_user_playlists(&self, limit: Option<u32>, offset: Option<u32>) -> Result<Page<SimplifiedPlaylist>, ()> {
self.api_with_retry(|api| api.current_user_playlists_manual(limit, offset)).await.ok_or(())
}

}

fn librespot_token_to_rspotify(token: &OAuthToken) -> Token {
Expand Down
12 changes: 9 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use api::SpotifyContext;
use auth::get_token;
use clap::Parser;
use cli::Args;
use cushy::{window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
use cushy::{value::Dynamic, widget::MakeWidget, window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
use librespot_core::{authentication::Credentials, Session, SessionConfig};
use librespot_playback::{audio_backend, config::{AudioFormat, PlayerConfig}, mixer::NoOpVolume, player::Player};
use widgets::{library::playlist::playlists_widget, ActivePage};

mod vibrancy;
mod theme;
Expand Down Expand Up @@ -64,9 +65,14 @@ fn main() -> cushy::Result {
dbg!(&user);
let userid = user.id;

format!("Hello, {}!", user.display_name.unwrap())
let playlists = context.current_user_playlists(None, None).await.unwrap();

let selected_page = Dynamic::new(ActivePage::default());

playlists_widget(playlists.items, selected_page)
.make_window()
.open(&mut app).unwrap();
.open(&mut app)
.unwrap();
});

drop(guard);
Expand Down
22 changes: 17 additions & 5 deletions src/widgets/image.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use cushy::{kludgine::{AnyTexture, LazyTexture}, value::{CallbackHandle, Destination, Dynamic, Source, Value}, widgets::Image};
use cushy::{kludgine::{AnyTexture, LazyTexture}, value::{CallbackDisconnected, CallbackHandle, Destination, Dynamic, Source, Value}, widgets::Image};
use futures_util::lock::Mutex;
use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions};
use image::imageops::FilterType;
Expand All @@ -10,18 +10,26 @@ use tokio::task::JoinHandle;

use crate::rt::tokio_runtime;


trait ImageExt {
pub trait ImageExt {
fn new_empty() -> Self;

fn load_url(&mut self, url: Dynamic<Option<String>>) -> CallbackHandle;

fn with_url(mut self, url: Dynamic<Option<String>>) -> Self
where Self: Sized
{
self.load_url(url).persist();
self
}
}

impl ImageExt for Image {
fn new_empty() -> Self {
Image::new(Dynamic::new(get_empty_texture()))
}

/// Makes the image connected to a URL
/// Calling this multiple times on a single image may cause memory leaks
fn load_url(&mut self, url: Dynamic<Option<String>>) -> CallbackHandle {
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
Expand All @@ -41,12 +49,15 @@ impl ImageExt for Image {
Value::Dynamic(dynamic) => dynamic,
_ => unreachable!()
};
let texture = texture.clone();

let prev_request_join = Arc::new(Mutex::new(None::<JoinHandle<()>>));
url.for_each({
url.for_each_try({
let texture = texture.clone();
move |url| {
let texture_count = texture.instances();
if texture_count <= 1 {
return Err(CallbackDisconnected);
}
let guard = tokio_runtime().enter();
let url = url.clone();
let prev_request_join = prev_request_join.clone();
Expand Down Expand Up @@ -74,6 +85,7 @@ impl ImageExt for Image {
}
});
drop(guard);
Ok(())
}
})
}
Expand Down
3 changes: 3 additions & 0 deletions src/widgets/library/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use rspotify::model::SimplifiedPlaylist;

pub mod playlist;
78 changes: 78 additions & 0 deletions src/widgets/library/playlist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use cushy::{styles::components::WidgetBackground, value::{Destination, Dynamic, IntoDynamic, IntoValue, Source, Value}, widget::{MakeWidget, WidgetList}, widgets::{grid::Orientation, Image, Stack}};
use rspotify::model::SimplifiedPlaylist;
use cushy::kludgine::Color;

use crate::widgets::{image::ImageExt, ActivePage, SelectedPage};

fn playlist_entry(playlist: impl IntoValue<SimplifiedPlaylist>, selected_page: SelectedPage) -> impl MakeWidget {
let playlist: Value<SimplifiedPlaylist> = playlist.into_value();
let id = playlist.map(|p| p.id.clone());
let background = selected_page.map_each(move |page| {
match page {
ActivePage::Playlist(p) if p.id == id => {
Color(0xFFFFFF10)
}
_ => Color::CLEAR_WHITE
}
});
Image::new_empty()
.with_url(
playlist
.map_each(|playlist| playlist.images.first().map(|image| image.url.clone()))
.into_dynamic()
)

.and(
playlist
.map_each(|p| p.name.clone())
.align_left()
.expand()
)
.into_columns()
.into_button()
.on_click(move |_| {
selected_page.set(ActivePage::Playlist(playlist.get()));
})
.with(&WidgetBackground, background)
}

pub fn playlists_widget(playlists: impl IntoValue<Vec<SimplifiedPlaylist>>, selected_page: SelectedPage) -> impl MakeWidget {
let playlists: Value<Vec<SimplifiedPlaylist>> = playlists.into_value();
Stack::new(
Orientation::Row,
playlists
.map_each(move |t| {
let mut list = t.clone().into_iter().map(|playlist| playlist_entry(playlist, selected_page.clone())).collect::<WidgetList>();
list.insert(0, liked_songs_entry(selected_page.clone()));
list
})
)
.vertical_scroll()
.expand()
}

pub fn liked_songs_entry(selected_page: SelectedPage) -> impl MakeWidget {
let background = selected_page.map_each(move |page| {
match page {
ActivePage::LikedSongs => {
Color(0xFFFFFF10)
}
_ => Color::CLEAR_WHITE
}
});
Image::new_empty()
.with_url(
Dynamic::new(Some("https://misc.scdn.co/liked-songs/liked-songs-300.png".to_string()))
)
.and(
"Liked Songs"
.align_left()
.expand()
)
.into_columns()
.into_button()
.on_click(move |_| {
selected_page.set(ActivePage::LikedSongs);
})
.with(&WidgetBackground, background)
}
16 changes: 15 additions & 1 deletion src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
pub mod image;
use cushy::value::Dynamic;
use rspotify::model::{SimplifiedAlbum, SimplifiedPlaylist};

pub mod image;
pub mod library;

#[derive(PartialEq, Debug, Default)]
pub enum ActivePage {
#[default]
LikedSongs,
Playlist(SimplifiedPlaylist),
Album(SimplifiedAlbum)
}

type SelectedPage = Dynamic<ActivePage>;

0 comments on commit 811ac8e

Please sign in to comment.