functional playlist list

This commit is contained in:
Daniel Bulant 2024-10-15 21:52:33 +02:00
parent 2963da1634
commit 811ac8eebf
No known key found for this signature in database
6 changed files with 127 additions and 10 deletions

View file

@ -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;
@ -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 {

View file

@ -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;
@ -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);

View file

@ -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;
@ -10,11 +10,17 @@ 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 {
@ -22,6 +28,8 @@ impl ImageExt for Image {
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 {
@ -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();
@ -74,6 +85,7 @@ impl ImageExt for Image {
}
});
drop(guard);
Ok(())
}
})
}

View file

@ -0,0 +1,3 @@
use rspotify::model::SimplifiedPlaylist;
pub mod playlist;

View file

@ -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)
}

View file

@ -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>;